From 05afacdd08d00d146d1a32522b794b23d9e98091 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ognjen=20Kati=C4=87?=
 <44910579+ognjenkatic@users.noreply.github.com>
Date: Fri, 1 Jul 2022 10:55:19 +0200
Subject: [PATCH 1/5] add basic tests

---
 ConductorSharp.sln                            |  6 ++
 .../ConductorSharp.Engine.Tests.csproj        | 40 ++++++++
 .../Integration/WorkflowBuilderTests.cs       | 17 ++++
 .../Samples/Tasks/CustomerGet.json            | 30 ++++++
 .../Samples/Tasks/CustomerGetV1.cs            | 31 +++++++
 .../Samples/Tasks/EmailPrepare.json           | 27 ++++++
 .../Samples/Tasks/EmailPrepareV1.cs           | 29 ++++++
 .../Samples/Workers/GetCustomerHandler.cs     | 28 ++++++
 .../Samples/Workers/PrepareEmailHandler.cs    | 18 ++++
 .../Workflows/SendCustomerNotification.cs     | 47 ++++++++++
 .../Workflows/SendCustomerNotification.json   | 91 +++++++++++++++++++
 .../Unit/TaskDefinitionBuilderTests.cs        | 16 ++++
 test/ConductorSharp.Engine.Tests/Usings.cs    |  9 ++
 .../Util/EmbeddedFileHelper.cs                | 72 +++++++++++++++
 14 files changed, 461 insertions(+)
 create mode 100644 test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
 create mode 100644 test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGet.json
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepare.json
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.json
 create mode 100644 test/ConductorSharp.Engine.Tests/Unit/TaskDefinitionBuilderTests.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Usings.cs
 create mode 100644 test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs

diff --git a/ConductorSharp.sln b/ConductorSharp.sln
index c0c687aa..2f246afe 100644
--- a/ConductorSharp.sln
+++ b/ConductorSharp.sln
@@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.NoApi", "exa
 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
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -47,6 +49,10 @@ Global
 		{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
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..15f84d9d
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
@@ -0,0 +1,40 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+
+    <IsPackable>false</IsPackable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="Samples\Tasks\CustomerGet.json" />
+    <None Remove="Samples\Tasks\EmailPrepare.json" />
+    <None Remove="Samples\Workflows\SendCustomerNotification.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <EmbeddedResource Include="Samples\Tasks\CustomerGet.json" />
+    <EmbeddedResource Include="Samples\Tasks\EmailPrepare.json" />
+	<EmbeddedResource Include="Samples\Workflows\SendCustomerNotification.json" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
+    <PackageReference Include="xunit" Version="2.4.1" />
+    <PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+      <PrivateAssets>all</PrivateAssets>
+    </PackageReference>
+    <PackageReference Include="coverlet.collector" Version="3.1.2">
+      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+      <PrivateAssets>all</PrivateAssets>
+    </PackageReference>
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\src\ConductorSharp.Engine\ConductorSharp.Engine.csproj" />
+  </ItemGroup>
+
+</Project>
diff --git a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
new file mode 100644
index 00000000..be45ff17
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
@@ -0,0 +1,17 @@
+﻿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..022e5a24
--- /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<CustomerGetV1Output>
+{
+    /// <originalName>
+    /// customer_id
+    /// </originalName>
+    public dynamic? CustomerId { get; set; }
+}
+
+public partial class CustomerGetV1Output
+{
+/// <originalName>
+/// name
+/// </originalName>
+public dynamic? Name { get; set; }
+
+/// <originalName>
+/// address
+/// </originalName>
+public dynamic? Address { get; set; }
+}
+
+/// <originalName>
+/// CUSTOMER_get
+/// </originalName>
+///
+[OriginalName("CUSTOMER_get")]
+public partial class CustomerGetV1 : SimpleTaskModel<CustomerGetV1Input, CustomerGetV1Output>
+{
+}
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..8a6310b8
--- /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<EmailPrepareV1Output>
+{
+    /// <originalName>
+    /// address
+    /// </originalName>
+    public dynamic? Address { get; set; }
+    /// <originalName>
+    /// name
+    /// </originalName>
+    public dynamic? Name { get; set; }
+}
+
+public partial class EmailPrepareV1Output
+{
+    /// <originalName>
+    /// email_body
+    /// </originalName>
+    public dynamic? EmailBody { get; set; }
+}
+
+/// <originalName>
+/// EMAIL_prepare
+/// </originalName>
+[OriginalName("EMAIL_prepare")]
+public partial class EmailPrepareV1 : SimpleTaskModel<EmailPrepareV1Input, EmailPrepareV1Output>
+{
+}
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..4faf9dec
--- /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<GetCustomerResponse>
+{
+    [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<GetCustomerRequest, GetCustomerResponse>
+{
+    public Task<GetCustomerResponse> 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..5c4cda46
--- /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<PrepareEmailResponse>
+{
+    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<PrepareEmailRequest, PrepareEmailResponse>
+{
+    public Task<PrepareEmailResponse> 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..660f85f7
--- /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<SendCustomerNotificationOutput>
+{
+    public dynamic? CustomerId { get; set; }
+}
+
+public class SendCustomerNotificationOutput : WorkflowOutput
+{
+    public dynamic? EmailBody { get; set; }
+}
+
+[OriginalName("NOTIFICATION_send_to_customer")]
+public class SendCustomerNotification
+    : Workflow<SendCustomerNotificationInput, SendCustomerNotificationOutput>
+{
+    public CustomerGetV1? GetCustomer { get; set; }
+    public EmailPrepareV1? PrepareEmail { get; set; }
+
+    public override WorkflowDefinition GetDefinition()
+    {
+        var builder = new WorkflowDefinitionBuilder<SendCustomerNotification>();
+
+        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<GetCustomerHandler>(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..28ca16ac
--- /dev/null
+++ b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
@@ -0,0 +1,72 @@
+﻿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<T>(
+            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 (templateParams != null)
+                foreach (var (Key, Value) in templateParams)
+                    contents = contents.Replace("{{" + Key + "}}", $"{Value}");
+
+            return JsonConvert.DeserializeObject<T>(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<T> GetObjectFromEmbeddedFileAsync<T>(
+            string fileName,
+            params (string Key, object Value)[] templateParams
+        ) => Task.FromResult(GetObjectFromEmbeddedFile<T>(fileName, templateParams));
+
+        public static string Reserialize<T>(
+            string fileName,
+            params (string Key, object Value)[] templateParams
+        )
+        {
+            var file = GetObjectFromEmbeddedFile<T>(fileName, templateParams);
+            return JsonConvert.SerializeObject(file);
+        }
+    }
+}
\ No newline at end of file

From 7e4aef3e9a5078ebcd222d5c087fd022d5c643a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ognjen=20Kati=C4=87?=
 <44910579+ognjenkatic@users.noreply.github.com>
Date: Fri, 1 Jul 2022 12:44:58 +0200
Subject: [PATCH 2/5] drop nullable

---
 .../ConductorSharp.Engine.Tests.csproj                    | 2 --
 .../Samples/Tasks/CustomerGetV1.cs                        | 6 +++---
 .../Samples/Tasks/EmailPrepareV1.cs                       | 6 +++---
 .../Samples/Workers/GetCustomerHandler.cs                 | 8 ++++----
 .../Samples/Workers/PrepareEmailHandler.cs                | 6 +++---
 .../Samples/Workflows/SendCustomerNotification.cs         | 8 ++++----
 .../Util/EmbeddedFileHelper.cs                            | 3 +++
 7 files changed, 20 insertions(+), 19 deletions(-)

diff --git a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
index 15f84d9d..e3aa706a 100644
--- a/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
+++ b/test/ConductorSharp.Engine.Tests/ConductorSharp.Engine.Tests.csproj
@@ -3,8 +3,6 @@
   <PropertyGroup>
     <TargetFramework>net6.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
-    <Nullable>enable</Nullable>
-
     <IsPackable>false</IsPackable>
   </PropertyGroup>
 
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs b/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs
index 022e5a24..c5c7d639 100644
--- a/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs
+++ b/test/ConductorSharp.Engine.Tests/Samples/Tasks/CustomerGetV1.cs
@@ -5,7 +5,7 @@ public partial class CustomerGetV1Input : IRequest<CustomerGetV1Output>
     /// <originalName>
     /// customer_id
     /// </originalName>
-    public dynamic? CustomerId { get; set; }
+    public dynamic CustomerId { get; set; }
 }
 
 public partial class CustomerGetV1Output
@@ -13,12 +13,12 @@ public partial class CustomerGetV1Output
 /// <originalName>
 /// name
 /// </originalName>
-public dynamic? Name { get; set; }
+public dynamic Name { get; set; }
 
 /// <originalName>
 /// address
 /// </originalName>
-public dynamic? Address { get; set; }
+public dynamic Address { get; set; }
 }
 
 /// <originalName>
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs b/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs
index 8a6310b8..9ad9db2d 100644
--- a/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs
+++ b/test/ConductorSharp.Engine.Tests/Samples/Tasks/EmailPrepareV1.cs
@@ -5,11 +5,11 @@ public partial class EmailPrepareV1Input : IRequest<EmailPrepareV1Output>
     /// <originalName>
     /// address
     /// </originalName>
-    public dynamic? Address { get; set; }
+    public dynamic Address { get; set; }
     /// <originalName>
     /// name
     /// </originalName>
-    public dynamic? Name { get; set; }
+    public dynamic Name { get; set; }
 }
 
 public partial class EmailPrepareV1Output
@@ -17,7 +17,7 @@ public partial class EmailPrepareV1Output
     /// <originalName>
     /// email_body
     /// </originalName>
-    public dynamic? EmailBody { get; set; }
+    public dynamic EmailBody { get; set; }
 }
 
 /// <originalName>
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs b/test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs
index 4faf9dec..cf4c50a9 100644
--- a/test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workers/GetCustomerHandler.cs
@@ -10,15 +10,15 @@ public class GetCustomerRequest : IRequest<GetCustomerResponse>
 
 public class GetCustomerResponse
 {
-    public string? Name { get; set; }
-    public string? Address { get; set; }
+    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; }
+    public string Name { get; set; }
+    public string Address { get; set; }
 }
 
 [OriginalName("CUSTOMER_get")]
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs b/test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs
index 5c4cda46..0c100b98 100644
--- a/test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workers/PrepareEmailHandler.cs
@@ -2,13 +2,13 @@
 
 public class PrepareEmailRequest : IRequest<PrepareEmailResponse>
 {
-    public string? CustomerName { get; set; }
-    public string? Address { get; set; }
+    public string CustomerName { get; set; }
+    public string Address { get; set; }
 }
 
 public class PrepareEmailResponse
 {
-    public string? EmailBody { get; set; }
+    public string EmailBody { get; set; }
 }
 
 [OriginalName("EMAIL_prepare")]
diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
index 660f85f7..0032517f 100644
--- a/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
@@ -2,20 +2,20 @@
 
 public class SendCustomerNotificationInput : WorkflowInput<SendCustomerNotificationOutput>
 {
-    public dynamic? CustomerId { get; set; }
+    public dynamic CustomerId { get; set; }
 }
 
 public class SendCustomerNotificationOutput : WorkflowOutput
 {
-    public dynamic? EmailBody { get; set; }
+    public dynamic EmailBody { get; set; }
 }
 
 [OriginalName("NOTIFICATION_send_to_customer")]
 public class SendCustomerNotification
     : Workflow<SendCustomerNotificationInput, SendCustomerNotificationOutput>
 {
-    public CustomerGetV1? GetCustomer { get; set; }
-    public EmailPrepareV1? PrepareEmail { get; set; }
+    public CustomerGetV1 GetCustomer { get; set; }
+    public EmailPrepareV1 PrepareEmail { get; set; }
 
     public override WorkflowDefinition GetDefinition()
     {
diff --git a/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
index 28ca16ac..52bcbfae 100644
--- a/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
+++ b/test/ConductorSharp.Engine.Tests/Util/EmbeddedFileHelper.cs
@@ -35,6 +35,9 @@ public static T GetObjectFromEmbeddedFile<T>(
 
             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}");

From e66e60f2cbbc2f81a7571b0a6ccdb5a6a0fa1679 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ognjen=20Kati=C4=87?=
 <44910579+ognjenkatic@users.noreply.github.com>
Date: Fri, 1 Jul 2022 12:45:12 +0200
Subject: [PATCH 3/5] add test action

---
 .github/workflows/dotnet.yml | 2 ++
 1 file changed, 2 insertions(+)

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

From 6e48547e1db43ce19e5e45e2d5e52f3deb33cb6f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ognjen=20Kati=C4=87?=
 <44910579+ognjenkatic@users.noreply.github.com>
Date: Fri, 1 Jul 2022 12:46:28 +0200
Subject: [PATCH 4/5] remove extra space

---
 .../Integration/WorkflowBuilderTests.cs                          | 1 -
 1 file changed, 1 deletion(-)

diff --git a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
index be45ff17..b2a725da 100644
--- a/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
+++ b/test/ConductorSharp.Engine.Tests/Integration/WorkflowBuilderTests.cs
@@ -11,7 +11,6 @@ public void BuilderReturnsCorrectDefinition()
             var expectedDefinition = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Samples/Workflows/SendCustomerNotification.json");
 
             Assert.Equal(expectedDefinition, definition);
-
         }
     }
 }

From 559e4c563b441c587501bba44dbcd6ad313ac240 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ognjen=20Kati=C4=87?=
 <44910579+ognjenkatic@users.noreply.github.com>
Date: Fri, 1 Jul 2022 12:47:51 +0200
Subject: [PATCH 5/5] drop exclamation

---
 .../Samples/Workflows/SendCustomerNotification.cs             | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
index 0032517f..2df608d2 100644
--- a/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
+++ b/test/ConductorSharp.Engine.Tests/Samples/Workflows/SendCustomerNotification.cs
@@ -31,8 +31,8 @@ public override WorkflowDefinition GetDefinition()
             b =>
                 new()
                 {
-                    Address = b.GetCustomer!.Output.Address,
-                    Name = b.GetCustomer!.Output.Name
+                    Address = b.GetCustomer.Output.Address,
+                    Name = b.GetCustomer.Output.Name
                 }
         );
 
