diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 47a169ed..048ecff4 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -21,3 +21,5 @@ jobs:
run: dotnet restore ConductorSharp.sln
- name: Build
run: dotnet build --no-restore ConductorSharp.sln
+ - name: Test
+ run: dotnet test --no-restore --verbosity normal ConductorSharp.sln
\ No newline at end of file
diff --git a/ConductorSharp.sln b/ConductorSharp.sln
index 7f457c40..f0f8822f 100644
--- a/ConductorSharp.sln
+++ b/ConductorSharp.sln
@@ -1,67 +1,73 @@
-
-Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
-VisualStudioVersion = 17.0.31919.166
-MinimumVisualStudioVersion = 10.0.40219.1
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Engine", "src\ConductorSharp.Engine\ConductorSharp.Engine.csproj", "{22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Client", "src\ConductorSharp.Client\ConductorSharp.Client.csproj", "{3244BEE8-17C4-4B00-BEB1-A41CC3E95482}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.ApiEnabled", "examples\ConductorSharp.ApiEnabled\ConductorSharp.ApiEnabled.csproj", "{C350E5E2-EA36-4816-BCBB-CBB597AAFF78}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Definitions", "examples\ConductorSharp.Definitions\ConductorSharp.Definitions.csproj", "{68400F84-CE5A-4FFA-A417-05C798ECD5A4}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{16F68738-1ED3-4646-BE01-DB9246C10E89}"
-EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.NoApi", "examples\ConductorSharp.NoApi\ConductorSharp.NoApi.csproj", "{44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}"
-EndProject
-Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{C398B7D9-C238-4169-86A8-B03D01FBB2BE}"
-EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DE1FA421-B8D6-45A7-9667-0798A241A89C}"
- ProjectSection(SolutionItems) = preProject
- .editorconfig = .editorconfig
- EndProjectSection
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.Build.0 = Release|Any CPU
- {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.Build.0 = Release|Any CPU
- {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.Build.0 = Release|Any CPU
- {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.Build.0 = Release|Any CPU
- {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.Build.0 = Release|Any CPU
- {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {C350E5E2-EA36-4816-BCBB-CBB597AAFF78} = {16F68738-1ED3-4646-BE01-DB9246C10E89}
- {68400F84-CE5A-4FFA-A417-05C798ECD5A4} = {16F68738-1ED3-4646-BE01-DB9246C10E89}
- {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9} = {16F68738-1ED3-4646-BE01-DB9246C10E89}
- EndGlobalSection
- GlobalSection(ExtensibilityGlobals) = postSolution
- SolutionGuid = {2443B3EC-E590-4C83-8BE2-A0787C66307C}
- EndGlobalSection
-EndGlobal
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31919.166
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Engine", "src\ConductorSharp.Engine\ConductorSharp.Engine.csproj", "{22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Client", "src\ConductorSharp.Client\ConductorSharp.Client.csproj", "{3244BEE8-17C4-4B00-BEB1-A41CC3E95482}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.ApiEnabled", "examples\ConductorSharp.ApiEnabled\ConductorSharp.ApiEnabled.csproj", "{C350E5E2-EA36-4816-BCBB-CBB597AAFF78}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Definitions", "examples\ConductorSharp.Definitions\ConductorSharp.Definitions.csproj", "{68400F84-CE5A-4FFA-A417-05C798ECD5A4}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{16F68738-1ED3-4646-BE01-DB9246C10E89}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.NoApi", "examples\ConductorSharp.NoApi\ConductorSharp.NoApi.csproj", "{44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}"
+EndProject
+Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{C398B7D9-C238-4169-86A8-B03D01FBB2BE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConductorSharp.Engine.Tests", "test\ConductorSharp.Engine.Tests\ConductorSharp.Engine.Tests.csproj", "{45AB7886-41DB-4A0E-9667-1C78D63F0FC7}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DE1FA421-B8D6-45A7-9667-0798A241A89C}"
+ ProjectSection(SolutionItems) = preProject
+ .editorconfig = .editorconfig
+ EndProjectSection
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.Build.0 = Release|Any CPU
+ {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.Build.0 = Release|Any CPU
+ {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.Build.0 = Release|Any CPU
+ {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C350E5E2-EA36-4816-BCBB-CBB597AAFF78} = {16F68738-1ED3-4646-BE01-DB9246C10E89}
+ {68400F84-CE5A-4FFA-A417-05C798ECD5A4} = {16F68738-1ED3-4646-BE01-DB9246C10E89}
+ {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9} = {16F68738-1ED3-4646-BE01-DB9246C10E89}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {2443B3EC-E590-4C83-8BE2-A0787C66307C}
+ EndGlobalSection
+EndGlobal
diff --git a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
new file mode 100644
index 00000000..e3aa706a
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
@@ -0,0 +1,38 @@
+
+
+
+ net6.0
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
diff --git a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
new file mode 100644
index 00000000..b2a725da
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
@@ -0,0 +1,16 @@
+using ConductorSharp.Engine.Tests.Samples.Workflows;
+
+namespace ConductorSharp.Engine.Tests.Integration
+{
+ public class WorkflowBuilderTests
+ {
+ [Fact]
+ public void BuilderReturnsCorrectDefinition()
+ {
+ var definition = JsonConvert.SerializeObject(new SendCustomerNotification().GetDefinition(), Formatting.Indented);
+ var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/SendCustomerNotification.json");
+
+ Assert.Equal(expectedDefinition, definition);
+ }
+ }
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGet.json b/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGet.json
new file mode 100644
index 00000000..c801cab1
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGet.json
@@ -0,0 +1,30 @@
+{
+ "ownerApp": "undefined",
+ "createTime": 0,
+ "updateTime": 0,
+ "createdBy": "UNDEFINED",
+ "updatedBy": "UNDEFINED",
+ "name": "CUSTOMER_get",
+ "description": "{\"description\":\"Missing description\"}",
+ "retryCount": 0,
+ "timeoutSeconds": 60,
+ "inputKeys": [
+ "customer_id"
+ ],
+ "outputKeys": [
+ "name",
+ "address"
+ ],
+ "timeoutPolicy": "TIME_OUT_WF",
+ "retryLogic": "FIXED",
+ "retryDelaySeconds": 0,
+ "responseTimeoutSeconds": 60,
+ "concurrentExecLimit": 0,
+ "inputTemplate": null,
+ "rateLimitPerFrequency": 0,
+ "rateLimitFrequencyInSeconds": 1,
+ "isolationGroupId": null,
+ "executionNameSpace": null,
+ "ownerEmail": "undefined@undefined.local",
+ "pollTimeoutSeconds": 0
+}
\ No newline at end of file
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs b/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs
new file mode 100644
index 00000000..c5c7d639
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs
@@ -0,0 +1,31 @@
+namespace ConductorSharp.Engine.Tests.Samples.Tasks;
+
+public partial class CustomerGetV1Input : IRequest
+{
+ ///
+ /// customer_id
+ ///
+ public dynamic CustomerId { get; set; }
+}
+
+public partial class CustomerGetV1Output
+{
+///
+/// name
+///
+public dynamic Name { get; set; }
+
+///
+/// address
+///
+public dynamic Address { get; set; }
+}
+
+///
+/// CUSTOMER_get
+///
+///
+[OriginalName("CUSTOMER_get")]
+public partial class CustomerGetV1 : SimpleTaskModel
+{
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepare.json b/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepare.json
new file mode 100644
index 00000000..50e935e3
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepare.json
@@ -0,0 +1,27 @@
+{
+ "ownerApp": "undefined",
+ "createTime": 1656663495488,
+ "createdBy": "",
+ "name": "EMAIL_prepare",
+ "description": "{\"description\":\"Missing description\"}",
+ "retryCount": 0,
+ "timeoutSeconds": 60,
+ "inputKeys": [
+ "customer_name",
+ "address"
+ ],
+ "outputKeys": [
+ "email_body"
+ ],
+ "timeoutPolicy": "TIME_OUT_WF",
+ "retryLogic": "FIXED",
+ "retryDelaySeconds": 0,
+ "responseTimeoutSeconds": 60,
+ "concurrentExecLimit": 0,
+ "inputTemplate": {},
+ "rateLimitPerFrequency": 0,
+ "rateLimitFrequencyInSeconds": 1,
+ "ownerEmail": "undefined@undefined.local",
+ "pollTimeoutSeconds": 0,
+ "backoffScaleFactor": 1
+}
\ No newline at end of file
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs b/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs
new file mode 100644
index 00000000..9ad9db2d
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs
@@ -0,0 +1,29 @@
+namespace ConductorSharp.Engine.Tests.Samples.Tasks;
+
+public partial class EmailPrepareV1Input : IRequest
+{
+ ///
+ /// address
+ ///
+ public dynamic Address { get; set; }
+ ///
+ /// name
+ ///
+ public dynamic Name { get; set; }
+}
+
+public partial class EmailPrepareV1Output
+{
+ ///
+ /// email_body
+ ///
+ public dynamic EmailBody { get; set; }
+}
+
+///
+/// EMAIL_prepare
+///
+[OriginalName("EMAIL_prepare")]
+public partial class EmailPrepareV1 : SimpleTaskModel
+{
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs b/test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs
new file mode 100644
index 00000000..cf4c50a9
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs
@@ -0,0 +1,28 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace ConductorSharp.Engine.Tests.Samples.Workers;
+
+public class GetCustomerRequest : IRequest
+{
+ [Required]
+ public int CustomerId { get; set; }
+}
+
+public class GetCustomerResponse
+{
+ public string Name { get; set; }
+ public string Address { get; set; }
+}
+
+public class Customer
+{
+ public int Id { get; set; }
+ public string Name { get; set; }
+ public string Address { get; set; }
+}
+
+[OriginalName("CUSTOMER_get")]
+public class GetCustomerHandler : ITaskRequestHandler
+{
+ public Task Handle(GetCustomerRequest request, CancellationToken cancellationToken) => throw new NotImplementedException();
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs b/test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs
new file mode 100644
index 00000000..0c100b98
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs
@@ -0,0 +1,18 @@
+namespace ConductorSharp.Engine.Tests.Samples.Workers;
+
+public class PrepareEmailRequest : IRequest
+{
+ public string CustomerName { get; set; }
+ public string Address { get; set; }
+}
+
+public class PrepareEmailResponse
+{
+ public string EmailBody { get; set; }
+}
+
+[OriginalName("EMAIL_prepare")]
+public class PrepareEmailHandler : ITaskRequestHandler
+{
+ public Task Handle(PrepareEmailRequest request, CancellationToken cancellationToken) => throw new NotImplementedException();
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
new file mode 100644
index 00000000..2df608d2
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
@@ -0,0 +1,47 @@
+namespace ConductorSharp.Engine.Tests.Samples.Workflows;
+
+public class SendCustomerNotificationInput : WorkflowInput
+{
+ public dynamic CustomerId { get; set; }
+}
+
+public class SendCustomerNotificationOutput : WorkflowOutput
+{
+ public dynamic EmailBody { get; set; }
+}
+
+[OriginalName("NOTIFICATION_send_to_customer")]
+public class SendCustomerNotification
+ : Workflow
+{
+ public CustomerGetV1 GetCustomer { get; set; }
+ public EmailPrepareV1 PrepareEmail { get; set; }
+
+ public override WorkflowDefinition GetDefinition()
+ {
+ var builder = new WorkflowDefinitionBuilder();
+
+ builder.AddTask(
+ a => a.GetCustomer,
+ b => new() { CustomerId = b.WorkflowInput.CustomerId }
+ );
+
+ builder.AddTask(
+ a => a.PrepareEmail,
+ b =>
+ new()
+ {
+ Address = b.GetCustomer.Output.Address,
+ Name = b.GetCustomer.Output.Name
+ }
+ );
+
+ return builder.Build(
+ options =>
+ {
+ options.Version = 1;
+ options.OwnerEmail = "example@example.local";
+ }
+ );
+ }
+}
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.json b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.json
new file mode 100644
index 00000000..83c0d144
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.json
@@ -0,0 +1,91 @@
+{
+ "ownerApp": null,
+ "createTime": 0,
+ "updateTime": 0,
+ "createdBy": null,
+ "updatedBy": null,
+ "name": "NOTIFICATION_send_to_customer",
+ "description": "{\"description\":null,\"labels\":null}",
+ "version": 1,
+ "tasks": [
+ {
+ "queryExpression": null,
+ "name": "CUSTOMER_get",
+ "taskReferenceName": "get_customer",
+ "description": "{\"description\":null}",
+ "inputParameters": {
+ "customer_id": "${workflow.input.customer_id}"
+ },
+ "type": "SIMPLE",
+ "dynamicTaskNameParam": null,
+ "caseValueParam": null,
+ "caseExpression": null,
+ "expression": null,
+ "evaluatorType": null,
+ "scriptExpression": null,
+ "decisionCases": null,
+ "dynamicForkJoinTasksParam": null,
+ "dynamicForkTasksParam": null,
+ "dynamicForkTasksInputParamName": null,
+ "defaultCase": null,
+ "forkTasks": null,
+ "startDelay": 0,
+ "subWorkflowParam": null,
+ "joinOn": null,
+ "sink": null,
+ "optional": false,
+ "taskDefinition": null,
+ "rateLimited": false,
+ "defaultExclusiveJoinTask": null,
+ "asyncComplete": false,
+ "loopCondition": null,
+ "loopOver": null
+ },
+ {
+ "queryExpression": null,
+ "name": "EMAIL_prepare",
+ "taskReferenceName": "prepare_email",
+ "description": "{\"description\":null}",
+ "inputParameters": {
+ "address": "${get_customer.output.address}",
+ "name": "${get_customer.output.name}"
+ },
+ "type": "SIMPLE",
+ "dynamicTaskNameParam": null,
+ "caseValueParam": null,
+ "caseExpression": null,
+ "expression": null,
+ "evaluatorType": null,
+ "scriptExpression": null,
+ "decisionCases": null,
+ "dynamicForkJoinTasksParam": null,
+ "dynamicForkTasksParam": null,
+ "dynamicForkTasksInputParamName": null,
+ "defaultCase": null,
+ "forkTasks": null,
+ "startDelay": 0,
+ "subWorkflowParam": null,
+ "joinOn": null,
+ "sink": null,
+ "optional": false,
+ "taskDefinition": null,
+ "rateLimited": false,
+ "defaultExclusiveJoinTask": null,
+ "asyncComplete": false,
+ "loopCondition": null,
+ "loopOver": null
+ }
+ ],
+ "inputParameters": [
+ "{\"customer_id\":{\"value\":\"\",\"description\":\" (optional)\"}}"
+ ],
+ "outputParameters": null,
+ "failureWorkflow": null,
+ "schemaVersion": 2,
+ "restartable": true,
+ "workflowStatusListenerEnabled": true,
+ "ownerEmail": "example@example.local",
+ "timeoutPolicy": null,
+ "timeoutSeconds": 0,
+ "variables": null
+}
\ No newline at end of file
diff --git a/test/ConductorSharp.Engine.Tests/Unit/TaskDefinitionBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Unit/TaskDefinitionBuilderTests.cs
new file mode 100644
index 00000000..2cf2a99b
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Unit/TaskDefinitionBuilderTests.cs
@@ -0,0 +1,16 @@
+using ConductorSharp.Engine.Tests.Samples.Workers;
+
+namespace ConductorSharp.Engine.Tests.Unit
+{
+ public class TaskDefinitionBuilderTests
+ {
+ [Fact]
+ public void ReturnsCorrectDefinition()
+ {
+ var definition = JsonConvert.SerializeObject(TaskDefinitionBuilder.Build(null), Formatting.Indented);
+ var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Tasks/CustomerGet.json");
+
+ Assert.Equal(expectedDefinition, definition);
+ }
+ }
+}
diff --git a/test/ConductorSharp.Engine.Tests/Usings.cs b/test/ConductorSharp.Engine.Tests/Usings.cs
new file mode 100644
index 00000000..4435fa02
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Usings.cs
@@ -0,0 +1,9 @@
+global using Xunit;
+global using ConductorSharp.Engine.Interface;
+global using ConductorSharp.Engine.Util;
+global using MediatR;
+global using ConductorSharp.Client.Model.Common;
+global using ConductorSharp.Engine.Builders;
+global using ConductorSharp.Engine.Tests.Samples.Tasks;
+global using ConductorSharp.Engine.Model;
+global using Newtonsoft.Json;
\ No newline at end of file
diff --git a/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
new file mode 100644
index 00000000..52bcbfae
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
@@ -0,0 +1,75 @@
+using Newtonsoft.Json;
+using System;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace ConductorSharp.Engine.Util
+{
+
+ internal static class EmbeddedFileHelper
+ {
+ private static string ReadAssemblyFile(Assembly assembly, string name)
+ {
+ var stream = assembly.GetManifestResourceStream(name);
+
+ if (stream == null)
+ throw new InvalidOperationException($"Resource {name} does not exist.");
+
+ using var reader = new StreamReader(stream, Encoding.UTF8);
+
+ return reader.ReadToEnd();
+ }
+
+ public static T GetObjectFromEmbeddedFile(
+ string fileName,
+ params (string Key, object Value)[] templateParams
+ )
+ {
+ fileName = fileName.Replace(
+ "~/",
+ typeof(EmbeddedFileHelper).Assembly.GetName().Name + "."
+ )
+ .Replace("/", ".");
+
+ var contents = ReadAssemblyFile(typeof(EmbeddedFileHelper).Assembly, fileName);
+
+ if (contents == null)
+ throw new Exception();
+
+ if (templateParams != null)
+ foreach (var (Key, Value) in templateParams)
+ contents = contents.Replace("{{" + Key + "}}", $"{Value}");
+
+ return JsonConvert.DeserializeObject(contents);
+ }
+
+ public static string GetLinesFromEmbeddedFile(string fileName)
+ {
+ fileName = fileName.Replace(
+ "~/",
+ typeof(EmbeddedFileHelper).Assembly.GetName().Name + "."
+ )
+ .Replace("/", ".");
+
+ var contents = ReadAssemblyFile(typeof(EmbeddedFileHelper).Assembly, fileName);
+
+ return contents;
+ }
+
+ public static Task GetObjectFromEmbeddedFileAsync(
+ string fileName,
+ params (string Key, object Value)[] templateParams
+ ) => Task.FromResult(GetObjectFromEmbeddedFile(fileName, templateParams));
+
+ public static string Reserialize(
+ string fileName,
+ params (string Key, object Value)[] templateParams
+ )
+ {
+ var file = GetObjectFromEmbeddedFile(fileName, templateParams);
+ return JsonConvert.SerializeObject(file);
+ }
+ }
+}
\ No newline at end of file