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
2 changes: 2 additions & 0 deletions WorkflowFramework.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@
<Project Path="tests/WorkflowFramework.Extensions.AI.Tests/WorkflowFramework.Extensions.AI.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Agents.Tests/WorkflowFramework.Extensions.Agents.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.HumanTasks.Tests/WorkflowFramework.Extensions.HumanTasks.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Connectors.Tests/WorkflowFramework.Extensions.Connectors.Tests.csproj" />
<Project Path="tests/WorkflowFramework.Extensions.Http.Tests/WorkflowFramework.Extensions.Http.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,120 @@
using FluentAssertions;
using NSubstitute;
using TinyBDD;
using TinyBDD.Xunit;
using WorkflowFramework.Extensions.Connectors.Abstractions;
using Xunit;
using Xunit.Abstractions;

namespace WorkflowFramework.Extensions.Connectors.Tests.Connectors;

[Feature("ConnectorRegistry — registers and retrieves connectors by name")]
public class ConnectorRegistryScenarios : TinyBddXunitBase
{
public ConnectorRegistryScenarios(ITestOutputHelper output) : base(output) { }

private static IConnector MakeConnector(string name, string type = "Test")
{
var c = Substitute.For<IConnector>();
c.Name.Returns(name);
c.Type.Returns(type);
c.TestConnectionAsync(Arg.Any<CancellationToken>()).Returns(Task.FromResult(true));
return c;
}

[Scenario("Get returns null for unregistered connector"), Fact]
public async Task Get_Unknown_ReturnsNull()
{
var registry = new ConnectorRegistry();

await Given("an empty registry", () => registry.Get("anything"))
.Then("null is returned", r =>
{
r.Should().BeNull();
return true;
})
.AssertPassed();
}

[Scenario("Register and Get retrieves the connector"), Fact]
public async Task Register_ThenGet_ReturnsConnector()
{
var registry = new ConnectorRegistry();
var connector = MakeConnector("my-connector");
registry.Register(connector);

await Given("a connector registered as 'my-connector'", () => registry.Get("my-connector"))
.Then("the same connector is returned", c =>
{
c.Should().BeSameAs(connector);
return true;
})
.AssertPassed();
}

[Scenario("Get is case-insensitive"), Fact]
public async Task Get_CaseInsensitive()
{
var registry = new ConnectorRegistry();
registry.Register(MakeConnector("MyConnector"));

await Given("connector registered as 'MyConnector'", () => registry.Get("myconnector"))
.Then("lowercase lookup returns the connector", c =>
{
c.Should().NotBeNull();
return true;
})
.AssertPassed();
}

[Scenario("Registering duplicate name throws InvalidOperationException"), Fact]
public async Task Register_Duplicate_Throws()
{
var registry = new ConnectorRegistry();
registry.Register(MakeConnector("dup"));
Exception? caught = null;
try { registry.Register(MakeConnector("dup")); }
catch (Exception ex) { caught = ex; }

await Given("two connectors with the same name registered", () => caught)
.Then("InvalidOperationException is thrown", ex =>
{
ex.Should().BeOfType<InvalidOperationException>();
return true;
})
.AssertPassed();
}

[Scenario("Register with null connector throws ArgumentNullException"), Fact]
public async Task Register_Null_Throws()
{
var registry = new ConnectorRegistry();
Exception? caught = null;
try { registry.Register(null!); }
catch (Exception ex) { caught = ex; }

await Given("null passed to Register", () => caught)
.Then("ArgumentNullException thrown", ex =>
{
ex.Should().BeOfType<ArgumentNullException>();
return true;
})
.AssertPassed();
}

[Scenario("Names reflects all registered connector names"), Fact]
public async Task Names_ReflectsAll()
{
var registry = new ConnectorRegistry();
registry.Register(MakeConnector("alpha"));
registry.Register(MakeConnector("beta"));

await Given("two connectors registered", () => registry.Names)
.Then("Names contains both", names =>
{
names.Should().Contain("alpha").And.Contain("beta");
return true;
})
.AssertPassed();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using FluentAssertions;
using System.Text;
using TinyBDD;
using TinyBDD.Xunit;
using WorkflowFramework.Extensions.Connectors.Abstractions;
using WorkflowFramework.Extensions.Connectors.Messaging;
using Xunit;
using Xunit.Abstractions;

namespace WorkflowFramework.Extensions.Connectors.Tests.Connectors;

[Feature("InMemoryMessageConnector — testable in-memory message connector")]
public class InMemoryMessageConnectorScenarios : TinyBddXunitBase
{
public InMemoryMessageConnectorScenarios(ITestOutputHelper output) : base(output) { }

[Scenario("Name defaults to 'in-memory'"), Fact]
public async Task Name_DefaultsToInMemory()
{
var connector = new InMemoryMessageConnector();

await Given("InMemoryMessageConnector with default name", () => connector.Name)
.Then("name is 'in-memory'", name =>
{
name.Should().Be("in-memory");
return true;
})
.AssertPassed();
}

[Scenario("Type is 'InMemory'"), Fact]
public async Task Type_IsInMemory()
{
var connector = new InMemoryMessageConnector();

await Given("a default InMemoryMessageConnector", () => connector.Type)
.Then("type is 'InMemory'", t =>
{
t.Should().Be("InMemory");
return true;
})
.AssertPassed();
}

[Scenario("ConnectAsync sets IsConnected to true"), Fact]
public async Task ConnectAsync_SetsIsConnectedTrue()
{
var connector = new InMemoryMessageConnector();
await connector.ConnectAsync();

await Given("ConnectAsync called", () => connector.IsConnected)
.Then("IsConnected is true", connected =>
{
connected.Should().BeTrue();
return true;
})
.AssertPassed();
}

[Scenario("DisconnectAsync sets IsConnected to false"), Fact]
public async Task DisconnectAsync_SetsIsConnectedFalse()
{
var connector = new InMemoryMessageConnector();
await connector.ConnectAsync();
await connector.DisconnectAsync();

await Given("DisconnectAsync called after Connect", () => connector.IsConnected)
.Then("IsConnected is false", connected =>
{
connected.Should().BeFalse();
return true;
})
.AssertPassed();
}

[Scenario("TestConnectionAsync returns true when connected"), Fact]
public async Task TestConnection_WhenConnected_ReturnsTrue()
{
var connector = new InMemoryMessageConnector();
await connector.ConnectAsync();
var result = await connector.TestConnectionAsync();

await Given("connector connected", () => result)
.Then("TestConnectionAsync returns true", r =>
{
r.Should().BeTrue();
return true;
})
.AssertPassed();
}

[Scenario("SendAsync throws when not connected"), Fact]
public async Task SendAsync_WhenNotConnected_Throws()
{
var connector = new InMemoryMessageConnector();
Exception? caught = null;
try { await connector.SendAsync("dest", Array.Empty<byte>()); }
catch (Exception ex) { caught = ex; }

await Given("connector not connected", () => caught)
.Then("InvalidOperationException is thrown", ex =>
{
ex.Should().BeOfType<InvalidOperationException>();
return true;
})
.AssertPassed();
}

[Scenario("ReceiveAsync dequeues sent message"), Fact]
public async Task ReceiveAsync_DequeuesSentMessage()
{
var connector = new InMemoryMessageConnector();
await connector.ConnectAsync();
var payload = Encoding.UTF8.GetBytes("hello");
await connector.SendAsync("queue", payload);

var received = await connector.ReceiveAsync("queue", TimeSpan.FromSeconds(1));

await Given("message sent and then received from same queue", () => received)
.Then("received message has correct payload", msg =>
{
msg.Should().NotBeNull();
msg!.Payload.Should().Equal(payload);
return true;
})
.AssertPassed();
}

[Scenario("SubscribeAsync handler is invoked on send"), Fact]
public async Task SubscribeAsync_HandlerInvokedOnSend()
{
var connector = new InMemoryMessageConnector();
await connector.ConnectAsync();
ConnectorMessage? received = null;
await connector.SubscribeAsync("topic", msg => { received = msg; return Task.CompletedTask; });

await connector.SendAsync("topic", Encoding.UTF8.GetBytes("event"));

await Given("subscriber registered before SendAsync", () => received)
.Then("subscriber handler received the message", msg =>
{
msg.Should().NotBeNull();
return true;
})
.AssertPassed();
}

[Scenario("Custom name is reflected in Name property"), Fact]
public async Task CustomName_ReflectedInProperty()
{
var connector = new InMemoryMessageConnector("my-bus");

await Given("connector created with name 'my-bus'", () => connector.Name)
.Then("Name is 'my-bus'", name =>
{
name.Should().Be("my-bus");
return true;
})
.AssertPassed();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<RootNamespace>WorkflowFramework.Extensions.Connectors.Tests</RootNamespace>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<!-- Source projects under test -->
<ProjectReference Include="..\..\src\WorkflowFramework\WorkflowFramework.csproj" />
<ProjectReference Include="..\..\src\WorkflowFramework.Extensions.Connectors.Abstractions\WorkflowFramework.Extensions.Connectors.Abstractions.csproj" />
<ProjectReference Include="..\..\src\WorkflowFramework.Extensions.Connectors.Messaging\WorkflowFramework.Extensions.Connectors.Messaging.csproj" />
</ItemGroup>
<ItemGroup>
<!-- Test frameworks -->
<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" />
</ItemGroup>
</Project>
Loading
Loading