Files
agent-framework/dotnet/tests/Microsoft.Agents.AI.Workflows.UnitTests/WorkflowBuilderSmokeTests.cs
T
Victor Dibia 5a7ca13af6 feat: Add name and description support to workflows (#1183)
Add optional name and description fields to workflows in both Python and .NET implementations, matching the existing agent API pattern.

Python changes:
- Add name/description parameters to WorkflowBuilder.__init__
- Add name/description attributes to Workflow class
- Include name/description in to_dict() serialization
- Add WORKFLOW_NAME and WORKFLOW_DESCRIPTION OTEL attributes
- Add tests in test_serialization.py and test_workflow_observability.py

.NET changes:
- Add Name and Description properties to Workflow and Workflow<T>
- Add WithName() and WithDescription() fluent methods to WorkflowBuilder
- Add WorkflowName and WorkflowDescription OTEL tags
- Add test in WorkflowBuilderSmokeTests.cs

This enables applications like DevUI to display human-readable workflow names (e.g., 'Data Processing Pipeline') instead of auto-generated UUIDs (e.g., 'Workflow 50fdd917').

Fixes: #1181
2025-10-04 15:20:17 +00:00

116 lines
3.7 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using FluentAssertions;
namespace Microsoft.Agents.AI.Workflows.UnitTests;
public partial class WorkflowBuilderSmokeTests
{
private sealed class NoOpExecutor(string id) : Executor(id)
{
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
routeBuilder.AddHandler<object>(
(msg, ctx) => ctx.SendMessageAsync(msg));
}
private sealed class SomeOtherNoOpExecutor(string id) : Executor(id)
{
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
routeBuilder.AddHandler<object>(
(msg, ctx) => ctx.SendMessageAsync(msg));
}
[Fact]
public void Test_LateBinding_Executor()
{
Workflow workflow = new WorkflowBuilder("start")
.BindExecutor(new NoOpExecutor("start"))
.Build();
workflow.StartExecutorId.Should().Be("start");
workflow.Registrations.Should().HaveCount(1);
workflow.Registrations.Should().ContainKey("start");
workflow.Registrations["start"].ExecutorType.Should().Be<NoOpExecutor>();
}
[Fact]
public void Test_LateImplicitBinding_Executor()
{
NoOpExecutor start = new("start");
Workflow workflow = new WorkflowBuilder("start")
.AddEdge(start, start)
.Build();
workflow.StartExecutorId.Should().Be("start");
workflow.Registrations.Should().HaveCount(1);
workflow.Registrations.Should().ContainKey("start");
workflow.Registrations["start"].ExecutorType.Should().Be<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();
};
act.Should().Throw<InvalidOperationException>();
}
[Fact]
public void Test_RebindToSameish_Allowed()
{
NoOpExecutor executor1 = new("start");
Workflow workflow = new WorkflowBuilder("start")
.AddEdge(executor1, executor1)
.Build();
workflow.StartExecutorId.Should().Be("start");
workflow.Registrations.Should().HaveCount(1);
workflow.Registrations.Should().ContainKey("start");
workflow.Registrations["start"].ExecutorType.Should().Be<NoOpExecutor>();
}
[Fact]
public void Test_Workflow_NameAndDescription()
{
// Test with name and description
Workflow workflow1 = new WorkflowBuilder("start")
.WithName("Test Pipeline")
.WithDescription("Test workflow description")
.BindExecutor(new NoOpExecutor("start"))
.Build();
workflow1.Name.Should().Be("Test Pipeline");
workflow1.Description.Should().Be("Test workflow description");
// Test without (defaults to null)
Workflow workflow2 = new WorkflowBuilder("start2")
.BindExecutor(new NoOpExecutor("start2"))
.Build();
workflow2.Name.Should().BeNull();
workflow2.Description.Should().BeNull();
// Test with only name (no description)
Workflow workflow3 = new WorkflowBuilder("start3")
.WithName("Named Only")
.BindExecutor(new NoOpExecutor("start3"))
.Build();
workflow3.Name.Should().Be("Named Only");
workflow3.Description.Should().BeNull();
}
}