mirror of
https://github.com/microsoft/autogen.git
synced 2025-08-23 16:12:22 +00:00

During the recent fix to SendMessageAsync's recurrence, we added code to ensure blocking on Publish. This adds additional resilience to the Publish delivery, ensuring that every subscribed agent receives the message, regardless of errors in the middle.
185 lines
8.9 KiB
C#
185 lines
8.9 KiB
C#
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// PublishMessageTests.cs
|
|
|
|
using System.Reflection;
|
|
using FluentAssertions;
|
|
using Microsoft.AutoGen.Contracts;
|
|
using Microsoft.Extensions.Logging;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.AutoGen.Core.Tests;
|
|
|
|
public static class PublishTestsExtensions
|
|
{
|
|
public static async ValueTask RegisterReceiverAgent(this MessagingTestFixture fixture,
|
|
string? agentNameSuffix = null,
|
|
params string[] topicTypes)
|
|
{
|
|
await fixture.RegisterFactoryMapInstances($"{nameof(ReceiverAgent)}{agentNameSuffix ?? string.Empty}",
|
|
(id, runtime) => ValueTask.FromResult(new ReceiverAgent(id, runtime, string.Empty)));
|
|
|
|
foreach (string topicType in topicTypes)
|
|
{
|
|
await fixture.Runtime.AddSubscriptionAsync(new TypeSubscription(topicType, $"{nameof(ReceiverAgent)}{agentNameSuffix ?? string.Empty}"));
|
|
}
|
|
}
|
|
|
|
public static async ValueTask RegisterErrorAgent(this MessagingTestFixture fixture,
|
|
string? agentNameSuffix = null,
|
|
params string[] topicTypes)
|
|
{
|
|
await fixture.RegisterFactoryMapInstances($"{nameof(ErrorAgent)}{agentNameSuffix ?? string.Empty}",
|
|
(id, runtime) => ValueTask.FromResult(new ErrorAgent(id, runtime, string.Empty)));
|
|
|
|
foreach (string topicType in topicTypes)
|
|
{
|
|
await fixture.Runtime.AddSubscriptionAsync(new TypeSubscription(topicType, $"{nameof(ErrorAgent)}{agentNameSuffix ?? string.Empty}"));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Trait("Category", "UnitV2")]
|
|
public class PublishMessageTests
|
|
{
|
|
private sealed class PublisherAgent : BaseAgent, IHandle<BasicMessage>
|
|
{
|
|
private IList<TopicId> targetTopics;
|
|
|
|
public PublisherAgent(AgentId id, IAgentRuntime runtime, string description, IList<TopicId> targetTopics, ILogger<BaseAgent>? logger = null)
|
|
: base(id, runtime, description, logger)
|
|
{
|
|
this.targetTopics = targetTopics;
|
|
}
|
|
|
|
public async ValueTask HandleAsync(BasicMessage item, MessageContext messageContext)
|
|
{
|
|
foreach (TopicId targetTopic in targetTopics)
|
|
{
|
|
BasicMessage message = new BasicMessage { Content = $"@{targetTopic}: {item.Content}" };
|
|
await this.Runtime.PublishMessageAsync(message, targetTopic);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_PublishMessage_Success()
|
|
{
|
|
MessagingTestFixture fixture = new MessagingTestFixture();
|
|
|
|
await fixture.RegisterReceiverAgent(topicTypes: "TestTopic");
|
|
await fixture.RegisterReceiverAgent("2", topicTypes: "TestTopic");
|
|
|
|
await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" });
|
|
|
|
fixture.GetAgentInstances<ReceiverAgent>().Values
|
|
.Should().HaveCount(2, "Two agents should have been created")
|
|
.And.AllSatisfy(receiverAgent => receiverAgent.Messages
|
|
.Should().NotBeNull()
|
|
.And.HaveCount(1)
|
|
.And.ContainSingle(m => m.Content == "1"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_PublishMessage_SingleFailure()
|
|
{
|
|
MessagingTestFixture fixture = new MessagingTestFixture();
|
|
|
|
await fixture.RegisterErrorAgent(topicTypes: "TestTopic");
|
|
|
|
Func<Task> publishTask = async () => await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" });
|
|
|
|
// Test that we wrap single errors appropriately
|
|
(await publishTask.Should().ThrowAsync<AggregateException>())
|
|
.Which.Should().Match<AggregateException>(
|
|
ex => ex.InnerExceptions.Count == 1 &&
|
|
ex.InnerExceptions.All(
|
|
inEx => inEx is TargetInvocationException &&
|
|
((TargetInvocationException)inEx).InnerException is TestException));
|
|
|
|
fixture.GetAgentInstances<ErrorAgent>().Values.Should().ContainSingle()
|
|
.Which.DidThrow.Should().BeTrue("Agent should have thrown an exception");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_PublishMessage_MultipleFailures()
|
|
{
|
|
MessagingTestFixture fixture = new MessagingTestFixture();
|
|
|
|
await fixture.RegisterErrorAgent(topicTypes: "TestTopic");
|
|
await fixture.RegisterErrorAgent("2", topicTypes: "TestTopic");
|
|
|
|
Func<Task> publishTask = async () => await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" });
|
|
|
|
// What we are really testing here is that a single exception does not prevent sending to the remaining agents
|
|
(await publishTask.Should().ThrowAsync<AggregateException>())
|
|
.Which.Should().Match<AggregateException>(
|
|
ex => ex.InnerExceptions.Count == 2 &&
|
|
ex.InnerExceptions.All(
|
|
inEx => inEx is TargetInvocationException &&
|
|
((TargetInvocationException)inEx).InnerException is TestException));
|
|
|
|
fixture.GetAgentInstances<ErrorAgent>().Values
|
|
.Should().HaveCount(2)
|
|
.And.AllSatisfy(
|
|
agent => agent.DidThrow.Should().BeTrue("Agent should have thrown an exception"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_PublishMessage_MixedSuccessFailure()
|
|
{
|
|
MessagingTestFixture fixture = new MessagingTestFixture();
|
|
|
|
await fixture.RegisterReceiverAgent(topicTypes: "TestTopic");
|
|
await fixture.RegisterReceiverAgent("2", topicTypes: "TestTopic");
|
|
|
|
await fixture.RegisterErrorAgent(topicTypes: "TestTopic");
|
|
await fixture.RegisterErrorAgent("2", topicTypes: "TestTopic");
|
|
|
|
Func<Task> publicTask = async () => await fixture.RunPublishTestAsync(new TopicId("TestTopic"), new BasicMessage { Content = "1" });
|
|
|
|
// What we are really testing here is that raising exceptions does not prevent sending to the remaining agents
|
|
(await publicTask.Should().ThrowAsync<AggregateException>())
|
|
.Which.Should().Match<AggregateException>(
|
|
ex => ex.InnerExceptions.Count == 2 &&
|
|
ex.InnerExceptions.All(
|
|
inEx => inEx is TargetInvocationException &&
|
|
((TargetInvocationException)inEx).InnerException is TestException));
|
|
|
|
fixture.GetAgentInstances<ReceiverAgent>().Values
|
|
.Should().HaveCount(2, "Two ReceiverAgents should have been created")
|
|
.And.AllSatisfy(receiverAgent => receiverAgent.Messages
|
|
.Should().NotBeNull()
|
|
.And.HaveCount(1)
|
|
.And.ContainSingle(m => m.Content == "1"),
|
|
"ReceiverAgents should get published message regardless of ErrorAgents throwing exception.");
|
|
|
|
fixture.GetAgentInstances<ErrorAgent>().Values
|
|
.Should().HaveCount(2, "Two ErrorAgents should have been created")
|
|
.And.AllSatisfy(agent => agent.DidThrow.Should().BeTrue("ErrorAgent should have thrown an exception"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Test_PublishMessage_RecurrentPublishSucceeds()
|
|
{
|
|
MessagingTestFixture fixture = new MessagingTestFixture();
|
|
|
|
await fixture.RegisterFactoryMapInstances(nameof(PublisherAgent),
|
|
(id, runtime) => ValueTask.FromResult(new PublisherAgent(id, runtime, string.Empty, new List<TopicId> { new TopicId("TestTopic") })));
|
|
|
|
await fixture.Runtime.AddSubscriptionAsync(new TypeSubscription("RunTest", nameof(PublisherAgent)));
|
|
|
|
await fixture.RegisterReceiverAgent(topicTypes: "TestTopic");
|
|
await fixture.RegisterReceiverAgent("2", topicTypes: "TestTopic");
|
|
|
|
await fixture.RunPublishTestAsync(new TopicId("RunTest"), new BasicMessage { Content = "1" });
|
|
|
|
TopicId testTopicId = new TopicId("TestTopic");
|
|
fixture.GetAgentInstances<ReceiverAgent>().Values
|
|
.Should().HaveCount(2, "Two ReceiverAgents should have been created")
|
|
.And.AllSatisfy(receiverAgent => receiverAgent.Messages
|
|
.Should().NotBeNull()
|
|
.And.HaveCount(1)
|
|
.And.ContainSingle(m => m.Content == $"@{testTopicId}: 1"));
|
|
}
|
|
}
|