Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions WorkflowFramework.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@
<Project Path="tests/WorkflowFramework.Extensions.Persistence.Tests/WorkflowFramework.Extensions.Persistence.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Persistence.InMemory.Tests/WorkflowFramework.Extensions.Persistence.InMemory.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Persistence.Sqlite.Tests/WorkflowFramework.Extensions.Persistence.Sqlite.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Configuration.Tests/WorkflowFramework.Extensions.Configuration.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Scheduling.Tests/WorkflowFramework.Extensions.Scheduling.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Plugins.Tests/WorkflowFramework.Extensions.Plugins.Tests.csproj" />
</Folder>
<Folder Name="/benchmarks/">
<Project Path="benchmarks/WorkflowFramework.Benchmarks/WorkflowFramework.Benchmarks.csproj" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using FluentAssertions;
using TinyBDD;
using TinyBDD.Xunit;
using Xunit;
using Xunit.Abstractions;
using WorkflowFramework.Extensions.Configuration;

namespace WorkflowFramework.Extensions.Configuration.Tests.Configuration;

[Feature("JsonWorkflowDefinitionLoader — deserializes JSON workflow definitions")]
public class JsonWorkflowDefinitionLoaderScenarios : TinyBddXunitBase
{
public JsonWorkflowDefinitionLoaderScenarios(ITestOutputHelper output) : base(output) { }

private static readonly JsonWorkflowDefinitionLoader Loader = new();

[Scenario("loads name from JSON"), Fact]
public async Task LoadsNameFromJson()
{
var json = """{"name":"OrderFlow","steps":[]}""";
var def = Loader.Load(json);

await Given("JSON with name 'OrderFlow'", () => def)
.Then("definition Name is 'OrderFlow'", d =>
{
d.Name.Should().Be("OrderFlow");
return true;
})
.AssertPassed();
}

[Scenario("loads step list from JSON"), Fact]
public async Task LoadsStepListFromJson()
{
var json = """{"name":"W","steps":[{"type":"step","class":"MyStep"}]}""";
var def = Loader.Load(json);

await Given("JSON with one step definition", () => def)
.Then("Steps contains one entry", d =>
{
d.Steps.Should().HaveCount(1);
return true;
})
.AssertPassed();
}

[Scenario("step type and class are loaded correctly"), Fact]
public async Task StepTypeAndClassLoaded()
{
var json = """{"name":"W","steps":[{"type":"step","class":"DoThing","name":"MyStep"}]}""";
var def = Loader.Load(json);

await Given("JSON with step type='step', class='DoThing', name='MyStep'", () => def.Steps[0])
.Then("step definition has correct type, class, and name", s =>
{
s.Type.Should().Be("step");
s.Class.Should().Be("DoThing");
s.Name.Should().Be("MyStep");
return true;
})
.AssertPassed();
}

[Scenario("compensation flag defaults to false"), Fact]
public async Task CompensationDefaultsFalse()
{
var json = """{"name":"W","steps":[]}""";
var def = Loader.Load(json);

await Given("JSON without compensation field", () => def)
.Then("Compensation is false", d =>
{
d.Compensation.Should().BeFalse();
return true;
})
.AssertPassed();
}

[Scenario("compensation flag is loaded when set to true"), Fact]
public async Task CompensationFlagLoaded()
{
var json = """{"name":"W","steps":[],"compensation":true}""";
var def = Loader.Load(json);

await Given("JSON with compensation:true", () => def)
.Then("Compensation is true", d =>
{
d.Compensation.Should().BeTrue();
return true;
})
.AssertPassed();
}

[Scenario("invalid JSON throws exception"), Fact]
public async Task InvalidJsonThrows()
{
await Given("invalid JSON content", () => new JsonWorkflowDefinitionLoader())
.Then("Load throws an exception", l =>
{
var act = () => l.Load("{ INVALID JSON !");
act.Should().Throw<Exception>();
return true;
})
.AssertPassed();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using FluentAssertions;
using Microsoft.Extensions.DependencyInjection;
using TinyBDD;
using TinyBDD.Xunit;
using Xunit;
using Xunit.Abstractions;
using WorkflowFramework.Extensions.Configuration;

namespace WorkflowFramework.Extensions.Configuration.Tests.Configuration;

[Feature("WorkflowDefinitionLoaderServiceCollectionExtensions — DI registration helpers")]
public class ServiceCollectionExtensionsScenarios : TinyBddXunitBase
{
public ServiceCollectionExtensionsScenarios(ITestOutputHelper output) : base(output) { }

[Scenario("AddYamlWorkflowLoader registers IWorkflowDefinitionLoader as singleton"), Fact]
public async Task AddYamlWorkflowLoader_RegistersLoader()
{
var provider = new ServiceCollection()
.AddYamlWorkflowLoader()
.BuildServiceProvider();

await Given("AddYamlWorkflowLoader registered", () => provider)
.Then("IWorkflowDefinitionLoader is resolvable", p =>
{
var loader = p.GetService<IWorkflowDefinitionLoader>();
loader.Should().NotBeNull().And.BeOfType<YamlWorkflowDefinitionLoader>();
return true;
})
.AssertPassed();
}

[Scenario("AddJsonWorkflowLoader registers IWorkflowDefinitionLoader as singleton"), Fact]
public async Task AddJsonWorkflowLoader_RegistersLoader()
{
var provider = new ServiceCollection()
.AddJsonWorkflowLoader()
.BuildServiceProvider();

await Given("AddJsonWorkflowLoader registered", () => provider)
.Then("IWorkflowDefinitionLoader resolves to JsonWorkflowDefinitionLoader", p =>
{
var loader = p.GetService<IWorkflowDefinitionLoader>();
loader.Should().NotBeNull().And.BeOfType<JsonWorkflowDefinitionLoader>();
return true;
})
.AssertPassed();
}

[Scenario("AddStepRegistry registers IStepRegistry as singleton"), Fact]
public async Task AddStepRegistry_RegistersRegistry()
{
var provider = new ServiceCollection()
.AddStepRegistry()
.BuildServiceProvider();

await Given("AddStepRegistry registered", () => provider)
.Then("IStepRegistry is resolvable", p =>
{
var registry = p.GetService<IStepRegistry>();
registry.Should().NotBeNull();
return true;
})
.AssertPassed();
}

[Scenario("AddWorkflowDefinitionBuilder registers WorkflowDefinitionBuilder as transient"), Fact]
public async Task AddWorkflowDefinitionBuilder_RegistersBuilder()
{
var provider = new ServiceCollection()
.AddStepRegistry()
.AddWorkflowDefinitionBuilder()
.BuildServiceProvider();

await Given("AddWorkflowDefinitionBuilder registered", () => provider)
.Then("WorkflowDefinitionBuilder is resolvable", p =>
{
var builder = p.GetService<WorkflowDefinitionBuilder>();
builder.Should().NotBeNull();
return true;
})
.AssertPassed();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using FluentAssertions;
using TinyBDD;
using TinyBDD.Xunit;
using Xunit;
using Xunit.Abstractions;
using WorkflowFramework.Extensions.Configuration;

namespace WorkflowFramework.Extensions.Configuration.Tests.Configuration;

[Feature("StepRegistry — runtime step type resolution by name")]
public class StepRegistryScenarios : TinyBddXunitBase
{
public StepRegistryScenarios(ITestOutputHelper output) : base(output) { }

private sealed class PingStep : IStep
{
public string Name => "ping";
public Task ExecuteAsync(IWorkflowContext context) => Task.CompletedTask;
}

private sealed class PongStep : IStep
{
public string Name => "pong";
public Task ExecuteAsync(IWorkflowContext context) => Task.CompletedTask;
}

[Scenario("generic Register<T> uses type name as default key"), Fact]
public async Task GenericRegisterUsesTypeName()
{
var registry = new StepRegistry();
registry.Register<PingStep>();

await Given("a registry with PingStep registered via generic overload", () => registry)
.Then("Resolve('PingStep') returns a PingStep", r =>
{
var step = r.Resolve("PingStep");
step.Should().BeOfType<PingStep>();
return true;
})
.AssertPassed();
}

[Scenario("generic Register<T> with explicit name uses that name"), Fact]
public async Task GenericRegisterWithExplicitName()
{
var registry = new StepRegistry();
registry.Register<PingStep>("my-ping");

await Given("a registry with PingStep registered as 'my-ping'", () => registry)
.Then("Resolve('my-ping') returns a PingStep", r =>
{
var step = r.Resolve("my-ping");
step.Should().BeOfType<PingStep>();
return true;
})
.AssertPassed();
}

[Scenario("factory-based Register delegates to provided factory"), Fact]
public async Task FactoryRegisterDelegates()
{
var registry = new StepRegistry();
registry.Register("custom", () => new PongStep());

await Given("a registry with factory-based 'custom' registration", () => registry)
.Then("Resolve('custom') returns a PongStep", r =>
{
var step = r.Resolve("custom");
step.Should().BeOfType<PongStep>();
return true;
})
.AssertPassed();
}

[Scenario("Resolve is case-insensitive"), Fact]
public async Task ResolveIsCaseInsensitive()
{
var registry = new StepRegistry();
registry.Register<PingStep>("Ping");

await Given("a registry with step registered as 'Ping' (mixed case)", () => registry)
.Then("Resolve('ping') succeeds despite case difference", r =>
{
var step = r.Resolve("ping");
step.Should().NotBeNull();
return true;
})
.AssertPassed();
}

[Scenario("Resolve throws KeyNotFoundException for unknown name"), Fact]
public async Task ResolveThrowsForUnknown()
{
var registry = new StepRegistry();

await Given("an empty registry", () => registry)
.Then("Resolve('ghost') throws KeyNotFoundException", r =>
{
var act = () => r.Resolve("ghost");
act.Should().Throw<KeyNotFoundException>();
return true;
})
.AssertPassed();
}

[Scenario("Names contains all registered step names"), Fact]
public async Task NamesContainsAllRegistered()
{
var registry = new StepRegistry();
registry.Register<PingStep>("ping");
registry.Register<PongStep>("pong");

await Given("a registry with two steps registered", () => registry.Names)
.Then("Names contains both 'ping' and 'pong'", names =>
{
names.Should().Contain("ping").And.Contain("pong");
return true;
})
.AssertPassed();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>WorkflowFramework.Extensions.Configuration.Tests</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\WorkflowFramework\WorkflowFramework.csproj" />
<ProjectReference Include="..\..\src\WorkflowFramework.Extensions.Configuration\WorkflowFramework.Extensions.Configuration.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="TinyBDD" />
<PackageReference Include="TinyBDD.Xunit" />
<PackageReference Include="xunit" />
<PackageReference Include="xunit.runner.visualstudio" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="FluentAssertions" />
<PackageReference Include="NSubstitute" />
<PackageReference Include="coverlet.collector" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" />
</ItemGroup>
</Project>
Loading
Loading