Files
wehub-resource-sync db620d33df
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:39:25 +08:00

194 lines
7.6 KiB
C#

// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using FluentAssertions;
using Microsoft.Extensions.AI;
#pragma warning disable SYSLIB1045 // Use GeneratedRegex
#pragma warning disable RCS1186 // Use Regex instance instead of static method
namespace Microsoft.Agents.AI.Workflows.UnitTests;
/// <summary>
/// Tests targeting the static <see cref="AgentWorkflowBuilder"/> helper surface —
/// <see cref="AgentWorkflowBuilder.BuildSequential(IEnumerable{AIAgent})"/>,
/// <see cref="AgentWorkflowBuilder.BuildConcurrent(IEnumerable{AIAgent}, Func{IList{List{ChatMessage}}, List{ChatMessage}})"/>,
/// and the various <c>Create*BuilderWith</c> factories. Per-builder unit tests live in their own
/// files (<see cref="SequentialWorkflowBuilderTests"/>, <see cref="ConcurrentWorkflowBuilderTests"/>, etc.).
/// </summary>
public class AgentWorkflowBuilderTests
{
[Fact]
public void Test_AgentWorkflowBuilder_BuildSequential_InvalidArguments_Throws()
{
Assert.Throws<ArgumentNullException>("agents", () => AgentWorkflowBuilder.BuildSequential(workflowName: null!, null!));
Assert.Throws<ArgumentException>("agents", () => AgentWorkflowBuilder.BuildSequential());
}
[Theory]
[InlineData(1)]
[InlineData(2)]
[InlineData(3)]
public async Task Test_AgentWorkflowBuilder_BuildSequential_DelegatesToBuilderAsync(int numAgents)
{
Workflow workflow = AgentWorkflowBuilder.BuildSequential(
from i in Enumerable.Range(1, numAgents)
select new OrchestrationTestHelpers.DoubleEchoAgent($"agent{i}"));
// Smoke: end-to-end run produces a non-empty result. Detailed pipeline-ordering
// assertions live in SequentialWorkflowBuilderTests.
(string updateText, List<ChatMessage>? result, _, _) =
await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "abc")]);
Assert.NotNull(result);
Assert.Equal(numAgents + 1, result.Count);
Assert.NotEmpty(updateText);
}
[Fact]
public async Task Test_AgentWorkflowBuilder_BuildSequential_ChainOnlyAgentResponsesAsync()
{
Workflow workflow = AgentWorkflowBuilder.BuildSequential(
chainOnlyAgentResponses: true,
new OrchestrationTestHelpers.DoubleEchoAgent("agent1"),
new OrchestrationTestHelpers.DoubleEchoAgent("agent2"));
(_, List<ChatMessage>? result, _, _) =
await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "abc")]);
result.Should().NotBeNull();
result!.Should().ContainSingle();
result[0].AuthorName.Should().Be("agent2");
}
[Fact]
public void Test_AgentWorkflowBuilder_BuildSequential_WithWorkflowNameSetsNameOnWorkflow()
{
Workflow workflow = AgentWorkflowBuilder.BuildSequential(
"static-sequential",
new OrchestrationTestHelpers.DoubleEchoAgent("agent1"));
workflow.Name.Should().Be("static-sequential");
}
[Fact]
public void Test_AgentWorkflowBuilder_BuildConcurrent_InvalidArguments_Throws()
{
Assert.Throws<ArgumentNullException>("agents", () => AgentWorkflowBuilder.BuildConcurrent(null!));
}
[Fact]
public async Task Test_AgentWorkflowBuilder_BuildConcurrent_DelegatesToBuilderAsync()
{
StrongBox<TaskCompletionSource<bool>> barrier = new();
StrongBox<int> remaining = new();
Workflow workflow = AgentWorkflowBuilder.BuildConcurrent(
[
new OrchestrationTestHelpers.DoubleEchoAgentWithBarrier("agent1", barrier, remaining),
new OrchestrationTestHelpers.DoubleEchoAgentWithBarrier("agent2", barrier, remaining),
]);
barrier.Value = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
remaining.Value = 2;
(string updateText, List<ChatMessage>? result, _, _) =
await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "abc")]);
Assert.NotEmpty(updateText);
Assert.NotNull(result);
Assert.Equal(2, result.Count);
Assert.Single(Regex.Matches(updateText, "agent1"));
Assert.Single(Regex.Matches(updateText, "agent2"));
}
[Fact]
public void Test_AgentWorkflowBuilder_BuildConcurrent_WithWorkflowNameSetsNameOnWorkflow()
{
Workflow workflow = AgentWorkflowBuilder.BuildConcurrent(
"static-concurrent",
[new OrchestrationTestHelpers.DoubleEchoAgent("agent1")]);
workflow.Name.Should().Be("static-concurrent");
}
[Fact]
public async Task Test_AgentWorkflowBuilder_BuildConcurrent_AggregatorIsHonoredAsync()
{
// Replace the default ("last message from each agent") with a custom aggregator,
// and confirm the workflow yields its result.
List<ChatMessage> sentinel = [new(ChatRole.Assistant, "custom-aggregator-result")];
Workflow workflow = AgentWorkflowBuilder.BuildConcurrent(
[new OrchestrationTestHelpers.DoubleEchoAgent("agent1")],
aggregator: _ => sentinel);
(_, List<ChatMessage>? result, _, _) =
await OrchestrationTestHelpers.RunWorkflowAsync(workflow, [new ChatMessage(ChatRole.User, "abc")]);
result.Should().NotBeNull().And.ContainSingle();
result![0].Text.Should().Be("custom-aggregator-result");
}
[Fact]
public void Test_AgentWorkflowBuilder_CreateSequentialBuilderWith_RejectsNull()
{
Assert.Throws<ArgumentNullException>("agents", () => AgentWorkflowBuilder.CreateSequentialBuilderWith(null!));
}
[Fact]
public void Test_AgentWorkflowBuilder_CreateSequentialBuilderWith_ReturnsConfigurableBuilder()
{
OrchestrationTestHelpers.DoubleEchoAgent agent = new("agent1");
SequentialWorkflowBuilder builder = AgentWorkflowBuilder.CreateSequentialBuilderWith(agent);
Workflow workflow = builder.WithName("via-factory").Build();
workflow.Name.Should().Be("via-factory");
}
[Fact]
public void Test_AgentWorkflowBuilder_CreateConcurrentBuilderWith_RejectsNull()
{
Assert.Throws<ArgumentNullException>("agents", () => AgentWorkflowBuilder.CreateConcurrentBuilderWith(null!));
}
[Fact]
public void Test_AgentWorkflowBuilder_CreateConcurrentBuilderWith_ReturnsConfigurableBuilder()
{
OrchestrationTestHelpers.DoubleEchoAgent agent = new("agent1");
ConcurrentWorkflowBuilder builder = AgentWorkflowBuilder.CreateConcurrentBuilderWith(agent);
Workflow workflow = builder.WithName("via-factory").Build();
workflow.Name.Should().Be("via-factory");
}
[Fact]
public void Test_AgentWorkflowBuilder_CreateHandoffBuilderWith_RejectsNull()
{
#pragma warning disable MAAIW001
Assert.Throws<ArgumentNullException>("initialAgent", () => AgentWorkflowBuilder.CreateHandoffBuilderWith(null!));
#pragma warning restore MAAIW001
}
[Fact]
public void Test_AgentWorkflowBuilder_CreateGroupChatBuilderWith_RejectsNull()
{
Assert.Throws<ArgumentNullException>("managerFactory", () => AgentWorkflowBuilder.CreateGroupChatBuilderWith(null!));
}
[Fact]
public void Test_AgentWorkflowBuilder_CreateMagenticBuilderWith_RejectsNull()
{
#pragma warning disable MAAIW001
Assert.Throws<ArgumentNullException>("managerAgent", () => AgentWorkflowBuilder.CreateMagenticBuilderWith(null!));
#pragma warning restore MAAIW001
}
}