Files
agent-framework/dotnet/tests/Microsoft.Agents.Workflows.UnitTests/WorkflowBuilderSmokeTests.cs
T
Jacob Alber 78125f019a .NET: Make WorkflowBuilder more intuititve (#503)
* feat: Make WorkflowBuilder more intutitve

Right now Executorish binding has some unintutitive behaviour. When a user adds an eecutor with an id of an executor that already exists, we silently replace it, if the user provides it inside of add_edge. When a user introduces an executor via an unbound id, the user must bind it via BindExecutor, even though the registration is created implicitly when an edge id added.

The change will remove the invisible update in favor of a "best efforts" check of type and instance equality.

* Expand errors when rebinding to disallowed

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-26 22:42:07 +00:00

95 lines
2.9 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using FluentAssertions;
namespace Microsoft.Agents.Workflows.UnitTests;
public partial class WorkflowBuilderSmokeTests
{
private sealed class NoOpExecutor(string? id = null) : Executor(id)
{
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder)
{
return routeBuilder.AddHandler<object>(
(msg, ctx) =>
{
return ctx.SendMessageAsync(msg);
});
}
}
private sealed class SomeOtherNoOpExecutor(string? id = null) : Executor(id)
{
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder)
{
return routeBuilder.AddHandler<object>(
(msg, ctx) =>
{
return ctx.SendMessageAsync(msg);
});
}
}
[Fact]
public void Test_LateBinding_Executor()
{
Workflow workflow = new WorkflowBuilder("start")
.BindExecutor(new NoOpExecutor("start"))
.Build<object>();
workflow.StartExecutorId.Should().Be("start");
workflow.Registrations.Should().HaveCount(1);
workflow.Registrations.Should().ContainKey("start");
workflow.Registrations["start"].ExecutorType.Should().Be(typeof(NoOpExecutor));
}
[Fact]
public void Test_LateImplicitBinding_Executor()
{
NoOpExecutor start = new("start");
Workflow workflow = new WorkflowBuilder("start")
.AddEdge(start, start)
.Build<object>();
workflow.StartExecutorId.Should().Be("start");
workflow.Registrations.Should().HaveCount(1);
workflow.Registrations.Should().ContainKey("start");
workflow.Registrations["start"].ExecutorType.Should().Be(typeof(NoOpExecutor));
}
[Fact]
public void Test_RebindToDifferent_Disallowed()
{
NoOpExecutor executor1 = new("start");
SomeOtherNoOpExecutor executor2 = new("start");
Func<Workflow> act = () =>
{
return new WorkflowBuilder("start")
.AddEdge(executor1, executor2)
.Build<object>();
};
act.Should().Throw<InvalidOperationException>();
}
[Fact]
public void Test_RebindToSameish_Allowed()
{
NoOpExecutor executor1 = new("start");
Workflow workflow = new WorkflowBuilder("start")
.AddEdge(executor1, executor1)
.Build<object>();
workflow.StartExecutorId.Should().Be("start");
workflow.Registrations.Should().HaveCount(1);
workflow.Registrations.Should().ContainKey("start");
workflow.Registrations["start"].ExecutorType.Should().Be(typeof(NoOpExecutor));
}
}