mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Enable more analyzers and various code tweaks/cleanup (#738)
This commit is contained in:
@@ -23,8 +23,5 @@ public abstract class AgentTests<TAgentFixture>(Func<TAgentFixture> createAgentF
|
||||
return this.Fixture.InitializeAsync();
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return this.Fixture.DisposeAsync();
|
||||
}
|
||||
public Task DisposeAsync() => this.Fixture.DisposeAsync();
|
||||
}
|
||||
|
||||
@@ -12,21 +12,14 @@ namespace AgentConformance.IntegrationTests;
|
||||
internal static class MenuPlugin
|
||||
{
|
||||
[Description("Provides a list of specials from the menu.")]
|
||||
public static string GetSpecials()
|
||||
{
|
||||
return
|
||||
"""
|
||||
Special Soup: Clam Chowder
|
||||
Special Salad: Cobb Salad
|
||||
Special Drink: Chai Tea
|
||||
""";
|
||||
}
|
||||
public static string GetSpecials() => """
|
||||
Special Soup: Clam Chowder
|
||||
Special Salad: Cobb Salad
|
||||
Special Drink: Chai Tea
|
||||
""";
|
||||
|
||||
[Description("Provides the price of the requested menu item.")]
|
||||
public static string GetItemPrice(
|
||||
[Description("The name of the menu item.")]
|
||||
string menuItem)
|
||||
{
|
||||
return "$9.99";
|
||||
}
|
||||
string menuItem) => "$9.99";
|
||||
}
|
||||
|
||||
@@ -85,15 +85,15 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
public virtual async Task ThreadMaintainsHistoryAsync()
|
||||
{
|
||||
// Arrange
|
||||
var q1 = "What is the capital of France.";
|
||||
var q2 = "And Austria?";
|
||||
const string Q1 = "What is the capital of France.";
|
||||
const string Q2 = "And Austria?";
|
||||
var agent = this.Fixture.Agent;
|
||||
var thread = agent.GetNewThread();
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var responseUpdates1 = await agent.RunStreamingAsync(q1, thread).ToListAsync();
|
||||
var responseUpdates2 = await agent.RunStreamingAsync(q2, thread).ToListAsync();
|
||||
var responseUpdates1 = await agent.RunStreamingAsync(Q1, thread).ToListAsync();
|
||||
var responseUpdates2 = await agent.RunStreamingAsync(Q2, thread).ToListAsync();
|
||||
|
||||
// Assert
|
||||
var response1Text = string.Concat(responseUpdates1.Select(x => x.Text));
|
||||
@@ -105,8 +105,8 @@ public abstract class RunStreamingTests<TAgentFixture>(Func<TAgentFixture> creat
|
||||
Assert.Equal(4, chatHistory.Count);
|
||||
Assert.Equal(2, chatHistory.Count(x => x.Role == ChatRole.User));
|
||||
Assert.Equal(2, chatHistory.Count(x => x.Role == ChatRole.Assistant));
|
||||
Assert.Equal(q1, chatHistory[0].Text);
|
||||
Assert.Equal(q2, chatHistory[2].Text);
|
||||
Assert.Equal(Q1, chatHistory[0].Text);
|
||||
Assert.Equal(Q2, chatHistory[2].Text);
|
||||
Assert.Contains("Paris", chatHistory[1].Text);
|
||||
Assert.Contains("Vienna", chatHistory[3].Text);
|
||||
}
|
||||
|
||||
@@ -92,15 +92,15 @@ public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFix
|
||||
public virtual async Task ThreadMaintainsHistoryAsync()
|
||||
{
|
||||
// Arrange
|
||||
var q1 = "What is the capital of France.";
|
||||
var q2 = "And Austria?";
|
||||
const string Q1 = "What is the capital of France.";
|
||||
const string Q2 = "And Austria?";
|
||||
var agent = this.Fixture.Agent;
|
||||
var thread = agent.GetNewThread();
|
||||
await using var cleanup = new ThreadCleanup(thread, this.Fixture);
|
||||
|
||||
// Act
|
||||
var result1 = await agent.RunAsync(q1, thread);
|
||||
var result2 = await agent.RunAsync(q2, thread);
|
||||
var result1 = await agent.RunAsync(Q1, thread);
|
||||
var result2 = await agent.RunAsync(Q2, thread);
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Paris", result1.Text);
|
||||
@@ -110,8 +110,8 @@ public abstract class RunTests<TAgentFixture>(Func<TAgentFixture> createAgentFix
|
||||
Assert.Equal(4, chatHistory.Count);
|
||||
Assert.Equal(2, chatHistory.Count(x => x.Role == ChatRole.User));
|
||||
Assert.Equal(2, chatHistory.Count(x => x.Role == ChatRole.Assistant));
|
||||
Assert.Equal(q1, chatHistory[0].Text);
|
||||
Assert.Equal(q2, chatHistory[2].Text);
|
||||
Assert.Equal(Q1, chatHistory[0].Text);
|
||||
Assert.Equal(Q2, chatHistory[2].Text);
|
||||
Assert.Contains("Paris", chatHistory[1].Text);
|
||||
Assert.Contains("Vienna", chatHistory[3].Text);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace AgentConformance.IntegrationTests.Support;
|
||||
/// <param name="fixture">The fixture that provides agent specific capabilities.</param>
|
||||
internal sealed class AgentCleanup(ChatClientAgent agent, IChatClientAgentFixture fixture) : IAsyncDisposable
|
||||
{
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
public async ValueTask DisposeAsync() =>
|
||||
await fixture.DeleteAgentAsync(agent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,10 +28,10 @@ public sealed class TestConfiguration
|
||||
var configType = typeof(T);
|
||||
var configTypeName = configType.Name;
|
||||
|
||||
var trimText = "Configuration";
|
||||
if (configTypeName.EndsWith(trimText, StringComparison.OrdinalIgnoreCase))
|
||||
const string TrimText = "Configuration";
|
||||
if (configTypeName.EndsWith(TrimText, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
configTypeName = configTypeName.Substring(0, configTypeName.Length - trimText.Length);
|
||||
configTypeName = configTypeName.Substring(0, configTypeName.Length - TrimText.Length);
|
||||
}
|
||||
|
||||
return s_configuration.GetRequiredSection(configTypeName).Get<T>() ??
|
||||
|
||||
@@ -13,8 +13,6 @@ namespace AgentConformance.IntegrationTests.Support;
|
||||
/// <param name="fixture">The fixture that provides agent specific capabilities.</param>
|
||||
internal sealed class ThreadCleanup(AgentThread thread, IAgentFixture fixture) : IAsyncDisposable
|
||||
{
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
public async ValueTask DisposeAsync() =>
|
||||
await fixture.DeleteThreadAsync(thread);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-7
@@ -30,9 +30,8 @@ public class AzureAIAgentsPersistentFixture : IChatClientAgentFixture
|
||||
{
|
||||
List<ChatMessage> messages = [];
|
||||
|
||||
AsyncPageable<PersistentThreadMessage> threadMessages = this._persistentAgentsClient.Messages.GetMessagesAsync(threadId: thread.ConversationId, order: ListSortOrder.Ascending);
|
||||
|
||||
await foreach (var threadMessage in threadMessages)
|
||||
await foreach (var threadMessage in (AsyncPageable<PersistentThreadMessage>)this._persistentAgentsClient.Messages.GetMessagesAsync(
|
||||
threadId: thread.ConversationId, order: ListSortOrder.Ascending))
|
||||
{
|
||||
var message = new ChatMessage
|
||||
{
|
||||
@@ -74,10 +73,8 @@ public class AzureAIAgentsPersistentFixture : IChatClientAgentFixture
|
||||
});
|
||||
}
|
||||
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent)
|
||||
{
|
||||
return this._persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
}
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent) =>
|
||||
this._persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
|
||||
|
||||
public Task DeleteThreadAsync(AgentThread thread)
|
||||
{
|
||||
|
||||
@@ -19,21 +19,16 @@ namespace CopilotStudio.IntegrationTests;
|
||||
public class CopilotStudioFixture : IAgentFixture
|
||||
{
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
private AIAgent _agent;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
public AIAgent Agent => this._agent;
|
||||
public AIAgent Agent { get; private set; } = null!;
|
||||
|
||||
public Task<List<ChatMessage>> GetChatHistoryAsync(AgentThread thread)
|
||||
{
|
||||
public Task<List<ChatMessage>> GetChatHistoryAsync(AgentThread thread) =>
|
||||
throw new NotSupportedException("CopilotStudio doesn't allow retrieval of chat history.");
|
||||
}
|
||||
|
||||
public Task DeleteThreadAsync(AgentThread thread)
|
||||
{
|
||||
public Task DeleteThreadAsync(AgentThread thread) =>
|
||||
// Chat Completion does not require/support deleting threads, so this is a no-op.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task InitializeAsync()
|
||||
{
|
||||
@@ -60,13 +55,10 @@ public class CopilotStudioFixture : IAgentFixture
|
||||
|
||||
CopilotClient client = new(settings, httpClientFactory, NullLogger.Instance, CopilotStudioHttpClientName);
|
||||
|
||||
this._agent = new CopilotStudioAgent(client);
|
||||
this.Agent = new CopilotStudioAgent(client);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -11,32 +11,22 @@ public class CopilotStudioRunStreamingTests() : RunStreamingTests<CopilotStudioF
|
||||
private const string ManualVerification = "For manual verification";
|
||||
|
||||
[Fact(Skip = "Copilot Studio does not support thread history retrieval, so this test is not applicable.")]
|
||||
public override Task ThreadMaintainsHistoryAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task ThreadMaintainsHistoryAsync() =>
|
||||
Task.CompletedTask;
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return base.RunWithChatMessageReturnsExpectedResultAsync();
|
||||
}
|
||||
public override Task RunWithChatMessageReturnsExpectedResultAsync() =>
|
||||
base.RunWithChatMessageReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessagesReturnsExpectedResultAsync()
|
||||
{
|
||||
return base.RunWithChatMessagesReturnsExpectedResultAsync();
|
||||
}
|
||||
public override Task RunWithChatMessagesReturnsExpectedResultAsync() =>
|
||||
base.RunWithChatMessagesReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return base.RunWithNoMessageDoesNotFailAsync();
|
||||
}
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
base.RunWithNoMessageDoesNotFailAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithStringReturnsExpectedResultAsync()
|
||||
{
|
||||
return base.RunWithStringReturnsExpectedResultAsync();
|
||||
}
|
||||
public override Task RunWithStringReturnsExpectedResultAsync() =>
|
||||
base.RunWithStringReturnsExpectedResultAsync();
|
||||
}
|
||||
|
||||
@@ -11,32 +11,22 @@ public class CopilotStudioRunTests() : RunTests<CopilotStudioFixture>(() => new(
|
||||
private const string ManualVerification = "For manual verification";
|
||||
|
||||
[Fact(Skip = "Copilot Studio does not support thread history retrieval, so this test is not applicable.")]
|
||||
public override Task ThreadMaintainsHistoryAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task ThreadMaintainsHistoryAsync() =>
|
||||
Task.CompletedTask;
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return base.RunWithChatMessageReturnsExpectedResultAsync();
|
||||
}
|
||||
public override Task RunWithChatMessageReturnsExpectedResultAsync() => base.RunWithChatMessageReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithChatMessagesReturnsExpectedResultAsync()
|
||||
{
|
||||
return base.RunWithChatMessagesReturnsExpectedResultAsync();
|
||||
}
|
||||
public override Task RunWithChatMessagesReturnsExpectedResultAsync() =>
|
||||
|
||||
base.RunWithChatMessagesReturnsExpectedResultAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return base.RunWithNoMessageDoesNotFailAsync();
|
||||
}
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
base.RunWithNoMessageDoesNotFailAsync();
|
||||
|
||||
[Fact(Skip = ManualVerification)]
|
||||
public override Task RunWithStringReturnsExpectedResultAsync()
|
||||
{
|
||||
return base.RunWithStringReturnsExpectedResultAsync();
|
||||
}
|
||||
public override Task RunWithStringReturnsExpectedResultAsync() =>
|
||||
base.RunWithStringReturnsExpectedResultAsync();
|
||||
}
|
||||
|
||||
@@ -135,8 +135,6 @@ internal sealed class CopilotStudioTokenHandler : HttpClientHandler
|
||||
storageProperties.WithMacKeyChain(KeyChainServiceName, KeyChainAccountName);
|
||||
}
|
||||
|
||||
MsalCacheHelper tokenCacheHelper = await MsalCacheHelper.CreateAsync(storageProperties.Build()).ConfigureAwait(false);
|
||||
|
||||
return tokenCacheHelper;
|
||||
return await MsalCacheHelper.CreateAsync(storageProperties.Build()).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
+16
-18
@@ -4,8 +4,6 @@ using System.Text.Json;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Storage.CosmosDB.Tests;
|
||||
|
||||
#pragma warning disable CA5394 // Insecure randomness is okay for test purposes
|
||||
|
||||
/// <summary>
|
||||
/// Integration tests for CosmosActorStateStorage focusing on concurrency control and ETag progression.
|
||||
/// </summary>
|
||||
@@ -34,20 +32,20 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
// Act - First write
|
||||
var operations1 = new List<ActorStateWriteOperation> { new SetValueOperation(key, value1) };
|
||||
var operations1 = new List<ActorStateWriteOperation> { new SetValueOperation(Key, value1) };
|
||||
var result1 = await storage.WriteStateAsync(testActorId, operations1, "0", cancellationToken);
|
||||
|
||||
// Act - Second write
|
||||
var operations2 = new List<ActorStateWriteOperation> { new SetValueOperation(key, value2) };
|
||||
var operations2 = new List<ActorStateWriteOperation> { new SetValueOperation(Key, value2) };
|
||||
var result2 = await storage.WriteStateAsync(testActorId, operations2, result1.ETag, cancellationToken);
|
||||
|
||||
// Act - Third write
|
||||
var operations3 = new List<ActorStateWriteOperation> { new RemoveKeyOperation(key) };
|
||||
var operations3 = new List<ActorStateWriteOperation> { new RemoveKeyOperation(Key) };
|
||||
var result3 = await storage.WriteStateAsync(testActorId, operations3, result2.ETag, cancellationToken);
|
||||
|
||||
// Assert
|
||||
@@ -188,11 +186,11 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// Act & Assert - Test null eTag (should create new document)
|
||||
@@ -230,7 +228,7 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
// Act & Assert - Test writing with correct eTag should succeed
|
||||
var updateOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, JsonSerializer.SerializeToElement("updatedValue"))
|
||||
new SetValueOperation(Key, JsonSerializer.SerializeToElement("updatedValue"))
|
||||
};
|
||||
var resultWithCorrectETag = await storage.WriteStateAsync(uniqueActorId2, updateOperations, resultWithInitialETag.ETag, cancellationToken);
|
||||
Assert.True(resultWithCorrectETag.Success);
|
||||
@@ -240,7 +238,7 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
// Verify the value was actually updated
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(uniqueActorId2, readOperations, cancellationToken);
|
||||
var getValue = readResult.Results[0] as GetValueResult;
|
||||
@@ -261,13 +259,13 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString()); // Fresh actor
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
|
||||
// Act - Read state from non-existent actor (this calls GetActorETagAsync internally)
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(testActorId, readOperations, cancellationToken);
|
||||
|
||||
@@ -281,7 +279,7 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
// Act - Write using the ETag from the read operation
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
var writeResult = await storage.WriteStateAsync(testActorId, writeOperations, readResult.ETag, cancellationToken);
|
||||
|
||||
@@ -311,16 +309,16 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString()); // Non-existent actor
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// Act - Try to write with a completely fabricated/invalid ETag (no document exists)
|
||||
var fabricatedETag = "\"fabricated-etag-12345\""; // Made-up ETag for non-existent document
|
||||
var resultWithFabricatedETag = await storage.WriteStateAsync(testActorId, operations, fabricatedETag, cancellationToken);
|
||||
const string FabricatedETag = "\"fabricated-etag-12345\""; // Made-up ETag for non-existent document
|
||||
var resultWithFabricatedETag = await storage.WriteStateAsync(testActorId, operations, FabricatedETag, cancellationToken);
|
||||
|
||||
// Assert - The write should fail due to ETag mismatch (document doesn't exist)
|
||||
Assert.False(resultWithFabricatedETag.Success);
|
||||
@@ -329,7 +327,7 @@ public class CosmosActorStateStorageConcurrencyTests
|
||||
// Verify no document was created
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(testActorId, readOperations, cancellationToken);
|
||||
var getValue = readResult.Results[0] as GetValueResult;
|
||||
|
||||
+30
-32
@@ -29,18 +29,18 @@ public class CosmosActorStateStorageListKeysTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var prefixKey1 = "prefix_key1";
|
||||
var prefixKey2 = "prefix_key2";
|
||||
var otherKey = "other_key";
|
||||
const string PrefixKey1 = "prefix_key1";
|
||||
const string PrefixKey2 = "prefix_key2";
|
||||
const string OtherKey = "other_key";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
var value3 = JsonSerializer.SerializeToElement("value3");
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(prefixKey1, value1),
|
||||
new SetValueOperation(prefixKey2, value2),
|
||||
new SetValueOperation(otherKey, value3)
|
||||
new SetValueOperation(PrefixKey1, value1),
|
||||
new SetValueOperation(PrefixKey2, value2),
|
||||
new SetValueOperation(OtherKey, value3)
|
||||
};
|
||||
|
||||
await storage.WriteStateAsync(testActorId, writeOperations, "0", cancellationToken);
|
||||
@@ -57,9 +57,9 @@ public class CosmosActorStateStorageListKeysTests
|
||||
var listKeys = result.Results[0] as ListKeysResult;
|
||||
Assert.NotNull(listKeys);
|
||||
Assert.Equal(2, listKeys.Keys.Count);
|
||||
Assert.Contains(prefixKey1, listKeys.Keys);
|
||||
Assert.Contains(prefixKey2, listKeys.Keys);
|
||||
Assert.DoesNotContain(otherKey, listKeys.Keys);
|
||||
Assert.Contains(PrefixKey1, listKeys.Keys);
|
||||
Assert.Contains(PrefixKey2, listKeys.Keys);
|
||||
Assert.DoesNotContain(OtherKey, listKeys.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -72,15 +72,15 @@ public class CosmosActorStateStorageListKeysTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1),
|
||||
new SetValueOperation(key2, value2)
|
||||
new SetValueOperation(Key1, value1),
|
||||
new SetValueOperation(Key2, value2)
|
||||
};
|
||||
|
||||
await storage.WriteStateAsync(testActorId, writeOperations, "0", cancellationToken);
|
||||
@@ -135,15 +135,15 @@ public class CosmosActorStateStorageListKeysTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1),
|
||||
new SetValueOperation(key2, value2)
|
||||
new SetValueOperation(Key1, value1),
|
||||
new SetValueOperation(Key2, value2)
|
||||
};
|
||||
|
||||
// First write some data
|
||||
@@ -162,8 +162,8 @@ public class CosmosActorStateStorageListKeysTests
|
||||
var listKeys = readResult.Results[0] as ListKeysResult;
|
||||
Assert.NotNull(listKeys);
|
||||
Assert.Equal(2, listKeys.Keys.Count);
|
||||
Assert.Contains(key1, listKeys.Keys);
|
||||
Assert.Contains(key2, listKeys.Keys);
|
||||
Assert.Contains(Key1, listKeys.Keys);
|
||||
Assert.Contains(Key2, listKeys.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -176,9 +176,9 @@ public class CosmosActorStateStorageListKeysTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
var key3 = "key3";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
const string Key3 = "key3";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
var value3 = JsonSerializer.SerializeToElement("value3");
|
||||
@@ -186,9 +186,9 @@ public class CosmosActorStateStorageListKeysTests
|
||||
// Setup initial state with 3 keys
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1),
|
||||
new SetValueOperation(key2, value2),
|
||||
new SetValueOperation(key3, value3)
|
||||
new SetValueOperation(Key1, value1),
|
||||
new SetValueOperation(Key2, value2),
|
||||
new SetValueOperation(Key3, value3)
|
||||
};
|
||||
var writeResult = await storage.WriteStateAsync(testActorId, writeOperations, "0", cancellationToken);
|
||||
Assert.True(writeResult.Success);
|
||||
@@ -196,7 +196,7 @@ public class CosmosActorStateStorageListKeysTests
|
||||
// Remove one key
|
||||
var removeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new RemoveKeyOperation(key2)
|
||||
new RemoveKeyOperation(Key2)
|
||||
};
|
||||
var removeResult = await storage.WriteStateAsync(testActorId, removeOperations, writeResult.ETag, cancellationToken);
|
||||
Assert.True(removeResult.Success);
|
||||
@@ -213,9 +213,9 @@ public class CosmosActorStateStorageListKeysTests
|
||||
var listKeys = readResult.Results[0] as ListKeysResult;
|
||||
Assert.NotNull(listKeys);
|
||||
Assert.Equal(2, listKeys.Keys.Count);
|
||||
Assert.Contains(key1, listKeys.Keys);
|
||||
Assert.Contains(key3, listKeys.Keys);
|
||||
Assert.DoesNotContain(key2, listKeys.Keys);
|
||||
Assert.Contains(Key1, listKeys.Keys);
|
||||
Assert.Contains(Key3, listKeys.Keys);
|
||||
Assert.DoesNotContain(Key2, listKeys.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -235,9 +235,7 @@ public class CosmosActorStateStorageListKeysTests
|
||||
string[] miscKeys = ["config", "metadata"];
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>();
|
||||
var allKeys = userKeys.Concat(sessionKeys).Concat(cacheKeys).Concat(miscKeys);
|
||||
|
||||
foreach (var key in allKeys)
|
||||
foreach (var key in userKeys.Concat(sessionKeys).Concat(cacheKeys).Concat(miscKeys))
|
||||
{
|
||||
writeOperations.Add(new SetValueOperation(key, JsonSerializer.SerializeToElement($"value_for_{key}")));
|
||||
}
|
||||
|
||||
+42
-45
@@ -29,12 +29,12 @@ public class CosmosActorStateStorageTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -55,15 +55,15 @@ public class CosmosActorStateStorageTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement(42);
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1),
|
||||
new SetValueOperation(key2, value2)
|
||||
new SetValueOperation(Key1, value1),
|
||||
new SetValueOperation(Key2, value2)
|
||||
};
|
||||
|
||||
// Act - Write state
|
||||
@@ -77,8 +77,8 @@ public class CosmosActorStateStorageTests
|
||||
// Act - Read individual values
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key1),
|
||||
new GetValueOperation(key2)
|
||||
new GetValueOperation(Key1),
|
||||
new GetValueOperation(Key2)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(testActorId, readOperations, cancellationToken);
|
||||
|
||||
@@ -105,14 +105,14 @@ public class CosmosActorStateStorageTests
|
||||
var listKeys = listResult.Results[0] as ListKeysResult;
|
||||
Assert.NotNull(listKeys);
|
||||
Assert.Equal(2, listKeys.Keys.Count);
|
||||
Assert.Contains(key1, listKeys.Keys);
|
||||
Assert.Contains(key2, listKeys.Keys);
|
||||
Assert.Contains(Key1, listKeys.Keys);
|
||||
Assert.Contains(Key2, listKeys.Keys);
|
||||
|
||||
// Act - Update with correct ETag
|
||||
var updateOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, JsonSerializer.SerializeToElement("updated_value1")),
|
||||
new RemoveKeyOperation(key2)
|
||||
new SetValueOperation(Key1, JsonSerializer.SerializeToElement("updated_value1")),
|
||||
new RemoveKeyOperation(Key2)
|
||||
};
|
||||
var updateResult = await storage.WriteStateAsync(testActorId, updateOperations, writeResult.ETag, cancellationToken);
|
||||
|
||||
@@ -123,8 +123,8 @@ public class CosmosActorStateStorageTests
|
||||
// Act - Verify final state
|
||||
var finalReadOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key1),
|
||||
new GetValueOperation(key2),
|
||||
new GetValueOperation(Key1),
|
||||
new GetValueOperation(Key2),
|
||||
new ListKeysOperation(continuationToken: null)
|
||||
};
|
||||
var finalResult = await storage.ReadStateAsync(testActorId, finalReadOperations, cancellationToken);
|
||||
@@ -143,8 +143,8 @@ public class CosmosActorStateStorageTests
|
||||
Assert.Equal("updated_value1", finalValue1.Value?.GetString());
|
||||
Assert.Null(finalValue2.Value); // key2 was removed
|
||||
Assert.Single(finalKeys.Keys);
|
||||
Assert.Contains(key1, finalKeys.Keys);
|
||||
Assert.DoesNotContain(key2, finalKeys.Keys);
|
||||
Assert.Contains(Key1, finalKeys.Keys);
|
||||
Assert.DoesNotContain(Key2, finalKeys.Keys);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -157,11 +157,11 @@ public class CosmosActorStateStorageTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// First write to establish state
|
||||
@@ -171,7 +171,7 @@ public class CosmosActorStateStorageTests
|
||||
// Act - Try to write with incorrect ETag
|
||||
var incorrectOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, JsonSerializer.SerializeToElement("newValue"))
|
||||
new SetValueOperation(Key, JsonSerializer.SerializeToElement("newValue"))
|
||||
};
|
||||
var result = await storage.WriteStateAsync(testActorId, incorrectOperations, "incorrect-etag", cancellationToken);
|
||||
|
||||
@@ -182,7 +182,7 @@ public class CosmosActorStateStorageTests
|
||||
// Verify original value is unchanged
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(testActorId, readOperations, cancellationToken);
|
||||
var getValue = readResult.Results[0] as GetValueResult;
|
||||
@@ -200,17 +200,17 @@ public class CosmosActorStateStorageTests
|
||||
var testActorId1 = new ActorId("TestActor1", Guid.NewGuid().ToString());
|
||||
var testActorId2 = new ActorId("TestActor2", Guid.NewGuid().ToString());
|
||||
|
||||
var key = "sharedKey";
|
||||
const string Key = "sharedKey";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
var operations1 = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value1)
|
||||
new SetValueOperation(Key, value1)
|
||||
};
|
||||
var operations2 = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value2)
|
||||
new SetValueOperation(Key, value2)
|
||||
};
|
||||
|
||||
// Act - Write to both actors
|
||||
@@ -220,7 +220,7 @@ public class CosmosActorStateStorageTests
|
||||
// Assert - Verify values are different
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
|
||||
var result1 = await storage.ReadStateAsync(testActorId1, readOperations, cancellationToken);
|
||||
@@ -246,10 +246,7 @@ public class CosmosActorStateStorageTests
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
var emptyOperations = new List<ActorStateWriteOperation>();
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
{
|
||||
await storage.WriteStateAsync(testActorId, emptyOperations, "0", cancellationToken);
|
||||
});
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(async () => await storage.WriteStateAsync(testActorId, emptyOperations, "0", cancellationToken));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -310,12 +307,12 @@ public class CosmosActorStateStorageTests
|
||||
};
|
||||
#pragma warning restore CA1861 // Avoid constant arrays as arguments
|
||||
|
||||
var key = "complexObject";
|
||||
const string Key = "complexObject";
|
||||
var value = JsonSerializer.SerializeToElement(complexObject);
|
||||
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// Act - Write complex object
|
||||
@@ -325,7 +322,7 @@ public class CosmosActorStateStorageTests
|
||||
// Act - Read back complex object
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(testActorId, readOperations, cancellationToken);
|
||||
|
||||
@@ -360,9 +357,9 @@ public class CosmosActorStateStorageTests
|
||||
await using var storage = new CosmosActorStateStorage(this._fixture.Container);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
var key3 = "key3";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
const string Key3 = "key3";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
var value3 = JsonSerializer.SerializeToElement("value3");
|
||||
@@ -370,11 +367,11 @@ public class CosmosActorStateStorageTests
|
||||
// Act - Perform multiple operations in a single batch
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1), // Set key1
|
||||
new SetValueOperation(key2, value2), // Set key2
|
||||
new SetValueOperation(key3, value3), // Set key3
|
||||
new RemoveKeyOperation(key1), // Remove key1
|
||||
new SetValueOperation(key1, JsonSerializer.SerializeToElement("new_value1")) // Re-add key1 with new value
|
||||
new SetValueOperation(Key1, value1), // Set key1
|
||||
new SetValueOperation(Key2, value2), // Set key2
|
||||
new SetValueOperation(Key3, value3), // Set key3
|
||||
new RemoveKeyOperation(Key1), // Remove key1
|
||||
new SetValueOperation(Key1, JsonSerializer.SerializeToElement("new_value1")) // Re-add key1 with new value
|
||||
};
|
||||
|
||||
var result = await storage.WriteStateAsync(testActorId, operations, "0", cancellationToken);
|
||||
@@ -385,9 +382,9 @@ public class CosmosActorStateStorageTests
|
||||
// Act - Verify final state
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key1),
|
||||
new GetValueOperation(key2),
|
||||
new GetValueOperation(key3),
|
||||
new GetValueOperation(Key1),
|
||||
new GetValueOperation(Key2),
|
||||
new GetValueOperation(Key3),
|
||||
new ListKeysOperation(continuationToken: null)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(testActorId, readOperations, cancellationToken);
|
||||
@@ -412,9 +409,9 @@ public class CosmosActorStateStorageTests
|
||||
|
||||
// All three keys should be present
|
||||
Assert.Equal(3, listKeys.Keys.Count);
|
||||
Assert.Contains(key1, listKeys.Keys);
|
||||
Assert.Contains(key2, listKeys.Keys);
|
||||
Assert.Contains(key3, listKeys.Keys);
|
||||
Assert.Contains(Key1, listKeys.Keys);
|
||||
Assert.Contains(Key2, listKeys.Keys);
|
||||
Assert.Contains(Key3, listKeys.Keys);
|
||||
}
|
||||
|
||||
[SkipOnEmulatorFact]
|
||||
|
||||
+1
-3
@@ -219,11 +219,9 @@ public class CosmosIdSanitizerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SeparatorChar_HasCorrectValue()
|
||||
{
|
||||
public void SeparatorChar_HasCorrectValue() =>
|
||||
// Assert
|
||||
Assert.Equal('_', CosmosIdSanitizer.SeparatorChar);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Sanitize_WithOnlySeparatorChar_EscapesCorrectly()
|
||||
|
||||
+2
-4
@@ -12,7 +12,7 @@ using Microsoft.Extensions.Logging;
|
||||
namespace Microsoft.Extensions.AI.Agents.Runtime.Storage.CosmosDB.Tests;
|
||||
|
||||
[CollectionDefinition("Cosmos Test Collection")]
|
||||
public class CosmosTests : ICollectionFixture<CosmosTestFixture> { }
|
||||
public class CosmosTests : ICollectionFixture<CosmosTestFixture>;
|
||||
|
||||
/// <summary>
|
||||
/// Shared test fixture for CosmosDB integration tests.
|
||||
@@ -41,9 +41,7 @@ public class CosmosTestFixture : IAsyncLifetime
|
||||
});
|
||||
|
||||
appHost.Services.ConfigureHttpClientDefaults(clientBuilder =>
|
||||
{
|
||||
clientBuilder.AddStandardResilienceHandler();
|
||||
});
|
||||
clientBuilder.AddStandardResilienceHandler());
|
||||
|
||||
this.App = await appHost.BuildAsync(cancellationToken).WaitAsync(cancellationToken);
|
||||
await this.App.StartAsync(cancellationToken).WaitAsync(cancellationToken);
|
||||
|
||||
+11
-19
@@ -77,11 +77,11 @@ public class LazyCosmosContainerTests
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
await using var storage = new CosmosActorStateStorage(lazyContainer);
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// This should work if the container was properly initialized
|
||||
@@ -185,32 +185,24 @@ public class LazyCosmosContainerTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullContainer_ShouldThrowArgumentNullException()
|
||||
{
|
||||
public void Constructor_WithNullContainer_ShouldThrowArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new LazyCosmosContainer((Container)null!));
|
||||
}
|
||||
Assert.Throws<ArgumentNullException>(() => new LazyCosmosContainer(null!));
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullCosmosClient_ShouldThrowArgumentNullException()
|
||||
{
|
||||
public void Constructor_WithNullCosmosClient_ShouldThrowArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new LazyCosmosContainer(null!, "test-db", "test-container"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullDatabaseName_ShouldThrowArgumentNullException()
|
||||
{
|
||||
public void Constructor_WithNullDatabaseName_ShouldThrowArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new LazyCosmosContainer(this._fixture.CosmosClient, null!, "test-container"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullContainerName_ShouldThrowArgumentNullException()
|
||||
{
|
||||
public void Constructor_WithNullContainerName_ShouldThrowArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new LazyCosmosContainer(this._fixture.CosmosClient, "test-db", null!));
|
||||
}
|
||||
|
||||
[SkipOnEmulatorFact]
|
||||
public async Task LazyCosmosContainer_WithInternalConstructor_ShouldWorkWithCosmosActorStateStorageAsync()
|
||||
@@ -229,11 +221,11 @@ public class LazyCosmosContainerTests
|
||||
await using var storage = new CosmosActorStateStorage(lazyContainer);
|
||||
var testActorId = new ActorId("TestActor", Guid.NewGuid().ToString());
|
||||
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// This should work - container should be initialized on first storage operation
|
||||
@@ -246,7 +238,7 @@ public class LazyCosmosContainerTests
|
||||
// Verify we can read back the value
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
var readResult = await storage.ReadStateAsync(testActorId, readOperations, cancellationToken);
|
||||
|
||||
@@ -281,6 +273,6 @@ public class LazyCosmosContainerTests
|
||||
await using var lazyContainer = new LazyCosmosContainer(this._fixture.CosmosClient, invalidDatabaseName, "test-container");
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<CosmosException>(async () => await lazyContainer.GetContainerAsync());
|
||||
await Assert.ThrowsAsync<CosmosException>(lazyContainer.GetContainerAsync);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,9 +136,7 @@ public sealed class HandoffOrchestrationTests : IDisposable
|
||||
.Build();
|
||||
|
||||
ChatClientAgentOptions agentOptions = new() { Name = name, Description = description };
|
||||
ChatClientAgent mockAgent = new(chatClient, agentOptions);
|
||||
|
||||
return mockAgent;
|
||||
return new(chatClient, agentOptions);
|
||||
}
|
||||
|
||||
private static class Responses
|
||||
|
||||
@@ -29,11 +29,9 @@ public class HandoffsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StartWith_NullAgent_ThrowsArgumentNullException()
|
||||
{
|
||||
public void StartWith_NullAgent_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>("initialAgent", () => Handoffs.StartWith(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Add_ValidSourceAndTargets_AddsHandoffRelationships()
|
||||
@@ -80,7 +78,7 @@ public class HandoffsTests
|
||||
var handoffs = Handoffs.StartWith(sourceAgent);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>("targets", () => handoffs.Add(sourceAgent, (AIAgent[])null!));
|
||||
Assert.Throws<ArgumentNullException>("targets", () => handoffs.Add(sourceAgent, null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -90,17 +88,17 @@ public class HandoffsTests
|
||||
var sourceAgent = CreateAgent("source", "Source agent");
|
||||
var targetAgent = CreateAgent("target", "Target agent");
|
||||
var handoffs = Handoffs.StartWith(sourceAgent);
|
||||
var customReason = "Custom handoff reason";
|
||||
const string CustomReason = "Custom handoff reason";
|
||||
|
||||
// Act
|
||||
var result = handoffs.Add(sourceAgent, targetAgent, customReason);
|
||||
var result = handoffs.Add(sourceAgent, targetAgent, CustomReason);
|
||||
|
||||
// Assert
|
||||
Assert.Same(handoffs, result);
|
||||
Assert.True(handoffs.Targets.ContainsKey(sourceAgent));
|
||||
var target = handoffs.Targets[sourceAgent].Single();
|
||||
Assert.Equal(targetAgent, target.Target);
|
||||
Assert.Equal(customReason, target.Reason);
|
||||
Assert.Equal(CustomReason, target.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -122,7 +120,7 @@ public class HandoffsTests
|
||||
var handoffs = Handoffs.StartWith(sourceAgent);
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>("target", () => handoffs.Add(sourceAgent, (AIAgent)null!, "reason"));
|
||||
Assert.Throws<ArgumentNullException>("target", () => handoffs.Add(sourceAgent, null!, "reason"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -159,15 +157,15 @@ public class HandoffsTests
|
||||
// Arrange
|
||||
var agent = CreateAgent("agent1", "Test agent");
|
||||
var handoffs = Handoffs.StartWith(agent);
|
||||
var orchestrationName = "Test Orchestration";
|
||||
const string OrchestrationName = "Test Orchestration";
|
||||
|
||||
// Act
|
||||
var orchestration = handoffs.Build(orchestrationName);
|
||||
var orchestration = handoffs.Build(OrchestrationName);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(orchestration);
|
||||
Assert.IsType<HandoffOrchestration>(orchestration);
|
||||
Assert.Equal(orchestrationName, orchestration.Name);
|
||||
Assert.Equal(OrchestrationName, orchestration.Name);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -196,9 +194,9 @@ public class HandoffsTests
|
||||
var sourceAgent1 = CreateAgent("source1", "Source agent 1");
|
||||
var sourceAgent2 = CreateAgent("source2", "Source agent 2");
|
||||
var targetAgent = CreateAgent("target", "Target agent");
|
||||
var handoffs = Handoffs.StartWith(sourceAgent1);
|
||||
handoffs.Add(sourceAgent1, targetAgent);
|
||||
handoffs.Add(sourceAgent2, targetAgent);
|
||||
var handoffs = Handoffs.StartWith(sourceAgent1)
|
||||
.Add(sourceAgent1, targetAgent)
|
||||
.Add(sourceAgent2, targetAgent);
|
||||
var readOnlyDict = (IReadOnlyDictionary<AIAgent, IEnumerable<Handoffs.HandoffTarget>>)handoffs;
|
||||
|
||||
// Act
|
||||
@@ -236,9 +234,9 @@ public class HandoffsTests
|
||||
var sourceAgent1 = CreateAgent("source1", "Source agent 1");
|
||||
var sourceAgent2 = CreateAgent("source2", "Source agent 2");
|
||||
var targetAgent = CreateAgent("target", "Target agent");
|
||||
var handoffs = Handoffs.StartWith(sourceAgent1);
|
||||
handoffs.Add(sourceAgent1, targetAgent);
|
||||
handoffs.Add(sourceAgent2, targetAgent);
|
||||
var handoffs = Handoffs.StartWith(sourceAgent1)
|
||||
.Add(sourceAgent1, targetAgent)
|
||||
.Add(sourceAgent2, targetAgent);
|
||||
var readOnlyCollection = (IReadOnlyCollection<KeyValuePair<AIAgent, IEnumerable<Handoffs.HandoffTarget>>>)handoffs;
|
||||
|
||||
// Act
|
||||
@@ -383,22 +381,20 @@ public class HandoffsTests
|
||||
{
|
||||
// Arrange
|
||||
var agent = CreateAgent("agent1", "Test agent");
|
||||
var reason = "Custom reason";
|
||||
const string Reason = "Custom reason";
|
||||
|
||||
// Act
|
||||
var target = new Handoffs.HandoffTarget(agent, reason);
|
||||
var target = new Handoffs.HandoffTarget(agent, Reason);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(agent, target.Target);
|
||||
Assert.Equal(reason, target.Reason);
|
||||
Assert.Equal(Reason, target.Reason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandoffTarget_Constructor_WithNullTarget_ThrowsArgumentNullException()
|
||||
{
|
||||
public void HandoffTarget_Constructor_WithNullTarget_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new Handoffs.HandoffTarget(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandoffTarget_Constructor_WithAgentWithoutDescriptionOrName_ThrowsInvalidOperationException()
|
||||
@@ -604,7 +600,6 @@ public class HandoffsTests
|
||||
Name = name,
|
||||
Description = description,
|
||||
};
|
||||
ChatClientAgent mockAgent = new(mockClient.Object, options);
|
||||
return mockAgent;
|
||||
return new(mockClient.Object, options);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,6 @@ internal sealed class HttpMessageHandlerStub : HttpMessageHandler
|
||||
{
|
||||
public Queue<HttpResponseMessage> ResponseQueue { get; } = new();
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
return Task.FromResult(this.ResponseQueue.Dequeue());
|
||||
}
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) =>
|
||||
Task.FromResult(this.ResponseQueue.Dequeue());
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.AI.Agents;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Agents.Orchestration.UnitTest;
|
||||
|
||||
@@ -15,13 +14,10 @@ namespace Microsoft.Agents.Orchestration.UnitTest;
|
||||
/// </summary>
|
||||
internal sealed class MockAgent(int index) : AIAgent
|
||||
{
|
||||
public static MockAgent CreateWithResponse(int index, string response)
|
||||
public static MockAgent CreateWithResponse(int index, string response) => new(index)
|
||||
{
|
||||
return new(index)
|
||||
{
|
||||
Response = [new(ChatRole.Assistant, response)]
|
||||
};
|
||||
}
|
||||
Response = [new(ChatRole.Assistant, response)]
|
||||
};
|
||||
|
||||
public int InvokeCount { get; private set; }
|
||||
|
||||
@@ -31,19 +27,11 @@ internal sealed class MockAgent(int index) : AIAgent
|
||||
|
||||
public override string? Description => $"test {index}";
|
||||
|
||||
public override AgentThread GetNewThread()
|
||||
{
|
||||
return new AgentThread() { ConversationId = Guid.NewGuid().ToString() };
|
||||
}
|
||||
public override AgentThread GetNewThread() => new() { ConversationId = Guid.NewGuid().ToString() };
|
||||
|
||||
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
this.InvokeCount++;
|
||||
if (thread == null)
|
||||
{
|
||||
Mock<AgentThread> mockThread = new(MockBehavior.Strict);
|
||||
thread = mockThread.Object;
|
||||
}
|
||||
|
||||
return Task.FromResult(new AgentRunResponse(messages: [.. this.Response]));
|
||||
}
|
||||
|
||||
+7
-7
@@ -25,10 +25,10 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output, AgentFixtu
|
||||
[Theory]
|
||||
[InlineData("SendActivity.yaml", "SendActivity.json")]
|
||||
[InlineData("InvokeAgent.yaml", "InvokeAgent.json")]
|
||||
public Task Validate(string workflowFileName, string testcaseFileName) =>
|
||||
this.RunWorkflow(workflowFileName, testcaseFileName);
|
||||
public Task ValidateAsync(string workflowFileName, string testcaseFileName) =>
|
||||
this.RunWorkflowAsync(workflowFileName, testcaseFileName);
|
||||
|
||||
private Task RunWorkflow(string workflowFileName, string testcaseFileName)
|
||||
private Task RunWorkflowAsync(string workflowFileName, string testcaseFileName)
|
||||
{
|
||||
this.Output.WriteLine($"WORKFLOW: {workflowFileName}");
|
||||
this.Output.WriteLine($"TESTCASE: {testcaseFileName}");
|
||||
@@ -42,13 +42,13 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output, AgentFixtu
|
||||
return
|
||||
testcase.Setup.Input.Type switch
|
||||
{
|
||||
nameof(ChatMessage) => this.RunWorkflow<ChatMessage>(testcase, workflowPath, configuration),
|
||||
nameof(String) => this.RunWorkflow<string>(testcase, workflowPath, configuration),
|
||||
nameof(ChatMessage) => this.RunWorkflowAsync<ChatMessage>(testcase, workflowPath, configuration),
|
||||
nameof(String) => this.RunWorkflowAsync<string>(testcase, workflowPath, configuration),
|
||||
_ => throw new NotSupportedException($"Input type '{testcase.Setup.Input.Type}' is not supported."),
|
||||
};
|
||||
}
|
||||
|
||||
private async Task RunWorkflow<TInput>(
|
||||
private async Task RunWorkflowAsync<TInput>(
|
||||
Testcase testcase,
|
||||
string workflowPath,
|
||||
IConfiguration configuration) where TInput : notnull
|
||||
@@ -58,7 +58,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output, AgentFixtu
|
||||
AzureAIConfiguration? foundryConfig = configuration.GetSection("AzureAI").Get<AzureAIConfiguration>();
|
||||
Assert.NotNull(foundryConfig);
|
||||
|
||||
IDictionary<string, string?> agentMap = await agentFixture.GetAgentsAsync(foundryConfig);
|
||||
IReadOnlyDictionary<string, string?> agentMap = await agentFixture.GetAgentsAsync(foundryConfig);
|
||||
|
||||
IConfiguration workflowConfig =
|
||||
new ConfigurationBuilder()
|
||||
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
@@ -20,7 +19,7 @@ namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
internal static class AgentFactory
|
||||
{
|
||||
public static async Task<ImmutableDictionary<string, string?>> CreateAsync(string agentsDirectory, AzureAIConfiguration config, CancellationToken cancellationToken)
|
||||
public static async Task<IReadOnlyDictionary<string, string?>> CreateAsync(string agentsDirectory, AzureAIConfiguration config, CancellationToken cancellationToken)
|
||||
{
|
||||
PersistentAgentsClient clientAgents = new(config.Endpoint, new AzureCliCredential());
|
||||
|
||||
@@ -45,6 +44,6 @@ internal static class AgentFactory
|
||||
agentMap[agent.Name] = agent.Id;
|
||||
}
|
||||
|
||||
return agentMap.ToImmutableDictionary();
|
||||
return agentMap;
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Shared.IntegrationTests;
|
||||
@@ -10,9 +10,9 @@ namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
public sealed class AgentFixture : IDisposable
|
||||
{
|
||||
private static ImmutableDictionary<string, string?>? s_agentMap;
|
||||
private static IReadOnlyDictionary<string, string?>? s_agentMap;
|
||||
|
||||
internal async Task<ImmutableDictionary<string, string?>> GetAgentsAsync(AzureAIConfiguration config, CancellationToken cancellationToken = default)
|
||||
internal async Task<IReadOnlyDictionary<string, string?>> GetAgentsAsync(AzureAIConfiguration config, CancellationToken cancellationToken = default)
|
||||
{
|
||||
s_agentMap ??= await AgentFactory.CreateAsync("Agents", config, cancellationToken);
|
||||
|
||||
|
||||
+9
-9
@@ -1,23 +1,23 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.Declarative.IntegrationTests.Framework;
|
||||
|
||||
internal sealed class WorkflowEvents
|
||||
{
|
||||
public WorkflowEvents(ImmutableList<WorkflowEvent> workflowEvents)
|
||||
public WorkflowEvents(IReadOnlyList<WorkflowEvent> workflowEvents)
|
||||
{
|
||||
this.Events = workflowEvents;
|
||||
this.EventCounts = workflowEvents.GroupBy(e => e.GetType()).ToImmutableDictionary(e => e.Key, e => e.Count());
|
||||
this.ActionInvokeEvents = workflowEvents.OfType<DeclarativeActionInvokedEvent>().ToImmutableList();
|
||||
this.ActionCompleteEvents = workflowEvents.OfType<DeclarativeActionCompletedEvent>().ToImmutableList();
|
||||
this.EventCounts = workflowEvents.GroupBy(e => e.GetType()).ToDictionary(e => e.Key, e => e.Count());
|
||||
this.ActionInvokeEvents = workflowEvents.OfType<DeclarativeActionInvokedEvent>().ToList();
|
||||
this.ActionCompleteEvents = workflowEvents.OfType<DeclarativeActionCompletedEvent>().ToList();
|
||||
}
|
||||
|
||||
public ImmutableList<WorkflowEvent> Events { get; }
|
||||
public IImmutableDictionary<Type, int> EventCounts { get; }
|
||||
public ImmutableList<DeclarativeActionInvokedEvent> ActionInvokeEvents { get; }
|
||||
public ImmutableList<DeclarativeActionCompletedEvent> ActionCompleteEvents { get; private set; }
|
||||
public IReadOnlyList<WorkflowEvent> Events { get; }
|
||||
public IReadOnlyDictionary<Type, int> EventCounts { get; }
|
||||
public IReadOnlyList<DeclarativeActionInvokedEvent> ActionInvokeEvents { get; }
|
||||
public IReadOnlyList<DeclarativeActionCompletedEvent> ActionCompleteEvents { get; }
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
@@ -11,7 +11,7 @@ internal static class WorkflowHarness
|
||||
public static async Task<WorkflowEvents> RunAsync<TInput>(Workflow<TInput> workflow, TInput input) where TInput : notnull
|
||||
{
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, input);
|
||||
ImmutableList<WorkflowEvent> workflowEvents = run.WatchStreamAsync().ToEnumerable().ToImmutableList();
|
||||
IReadOnlyList<WorkflowEvent> workflowEvents = run.WatchStreamAsync().ToEnumerable().ToList();
|
||||
return new WorkflowEvents(workflowEvents);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-6
@@ -29,23 +29,23 @@ public class DeclarativeWorkflowContextTests
|
||||
{
|
||||
// Arrange
|
||||
TokenCredential credentials = new DefaultAzureCredential();
|
||||
int maxCallDepth = 10;
|
||||
int maxExpressionLength = 100;
|
||||
const int MaxCallDepth = 10;
|
||||
const int MaxExpressionLength = 100;
|
||||
ILoggerFactory loggerFactory = LoggerFactory.Create(builder => { });
|
||||
|
||||
// Act
|
||||
Mock<WorkflowAgentProvider> mockProvider = new(MockBehavior.Strict);
|
||||
DeclarativeWorkflowOptions context = new(mockProvider.Object)
|
||||
{
|
||||
MaximumCallDepth = maxCallDepth,
|
||||
MaximumExpressionLength = maxExpressionLength,
|
||||
MaximumCallDepth = MaxCallDepth,
|
||||
MaximumExpressionLength = MaxExpressionLength,
|
||||
LoggerFactory = loggerFactory
|
||||
};
|
||||
|
||||
// Assert
|
||||
Assert.Equal(mockProvider.Object, context.AgentProvider);
|
||||
Assert.Equal(maxCallDepth, context.MaximumCallDepth);
|
||||
Assert.Equal(maxExpressionLength, context.MaximumExpressionLength);
|
||||
Assert.Equal(MaxCallDepth, context.MaximumCallDepth);
|
||||
Assert.Equal(MaxExpressionLength, context.MaximumExpressionLength);
|
||||
Assert.Same(loggerFactory, context.LoggerFactory);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -28,25 +28,25 @@ public sealed class DeclarativeWorkflowExceptionTest(ITestOutputHelper output) :
|
||||
|
||||
private static void AssertDefault<TException>(Action throwAction) where TException : Exception
|
||||
{
|
||||
TException exception = Assert.Throws<TException>(() => throwAction.Invoke());
|
||||
TException exception = Assert.Throws<TException>(throwAction.Invoke);
|
||||
Assert.NotEmpty(exception.Message);
|
||||
Assert.Null(exception.InnerException);
|
||||
}
|
||||
|
||||
private static void AssertMessage<TException>(Action<string> throwAction) where TException : Exception
|
||||
{
|
||||
const string message = "Test exception message";
|
||||
TException exception = Assert.Throws<TException>(() => throwAction.Invoke(message));
|
||||
Assert.Equal(message, exception.Message);
|
||||
const string Message = "Test exception message";
|
||||
TException exception = Assert.Throws<TException>(() => throwAction.Invoke(Message));
|
||||
Assert.Equal(Message, exception.Message);
|
||||
Assert.Null(exception.InnerException);
|
||||
}
|
||||
|
||||
private static void AssertInner<TException>(Action<string, Exception> throwAction) where TException : Exception
|
||||
{
|
||||
const string message = "Test exception message";
|
||||
const string Message = "Test exception message";
|
||||
NotSupportedException innerException = new("Inner exception message");
|
||||
TException exception = Assert.Throws<TException>(() => throwAction.Invoke(message, innerException));
|
||||
Assert.Equal(message, exception.Message);
|
||||
TException exception = Assert.Throws<TException>(() => throwAction.Invoke(Message, innerException));
|
||||
Assert.Equal(Message, exception.Message);
|
||||
Assert.Equal(innerException, exception.InnerException);
|
||||
}
|
||||
}
|
||||
|
||||
+29
-32
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
@@ -19,33 +19,33 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests;
|
||||
/// </summary>
|
||||
public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : WorkflowTest(output)
|
||||
{
|
||||
private ImmutableList<WorkflowEvent> WorkflowEvents { get; set; } = ImmutableList<WorkflowEvent>.Empty;
|
||||
private List<WorkflowEvent> WorkflowEvents { get; set; } = [];
|
||||
|
||||
private ImmutableDictionary<Type, int> WorkflowEventCounts { get; set; } = ImmutableDictionary<Type, int>.Empty;
|
||||
private Dictionary<Type, int> WorkflowEventCounts { get; set; } = [];
|
||||
|
||||
[Theory]
|
||||
[InlineData("BadEmpty.yaml")]
|
||||
[InlineData("BadId.yaml")]
|
||||
[InlineData("BadKind.yaml")]
|
||||
public async Task InvalidWorkflow(string workflowFile)
|
||||
public async Task InvalidWorkflowAsync(string workflowFile)
|
||||
{
|
||||
await Assert.ThrowsAsync<DeclarativeModelException>(() => this.RunWorkflow(workflowFile));
|
||||
await Assert.ThrowsAsync<DeclarativeModelException>(() => this.RunWorkflowAsync(workflowFile));
|
||||
this.AssertNotExecuted("end_all");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoopEachAction()
|
||||
public async Task LoopEachActionAsync()
|
||||
{
|
||||
await this.RunWorkflow("LoopEach.yaml");
|
||||
await this.RunWorkflowAsync("LoopEach.yaml");
|
||||
this.AssertExecutionCount(expectedCount: 35);
|
||||
this.AssertExecuted("foreach_loop");
|
||||
this.AssertExecuted("end_all");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoopBreakAction()
|
||||
public async Task LoopBreakActionAsync()
|
||||
{
|
||||
await this.RunWorkflow("LoopBreak.yaml");
|
||||
await this.RunWorkflowAsync("LoopBreak.yaml");
|
||||
this.AssertExecutionCount(expectedCount: 7);
|
||||
this.AssertExecuted("foreach_loop");
|
||||
this.AssertExecuted("breakLoop_now");
|
||||
@@ -55,9 +55,9 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LoopContinueAction()
|
||||
public async Task LoopContinueActionAsync()
|
||||
{
|
||||
await this.RunWorkflow("LoopContinue.yaml");
|
||||
await this.RunWorkflowAsync("LoopContinue.yaml");
|
||||
this.AssertExecutionCount(expectedCount: 7);
|
||||
this.AssertExecuted("foreach_loop");
|
||||
this.AssertExecuted("continueLoop_now");
|
||||
@@ -67,18 +67,18 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EndConversationAction()
|
||||
public async Task EndConversationActionAsync()
|
||||
{
|
||||
await this.RunWorkflow("EndConversation.yaml");
|
||||
await this.RunWorkflowAsync("EndConversation.yaml");
|
||||
this.AssertExecutionCount(expectedCount: 1);
|
||||
this.AssertExecuted("end_all");
|
||||
this.AssertNotExecuted("sendActivity_1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GotoAction()
|
||||
public async Task GotoActionAsync()
|
||||
{
|
||||
await this.RunWorkflow("Goto.yaml");
|
||||
await this.RunWorkflowAsync("Goto.yaml");
|
||||
this.AssertExecutionCount(expectedCount: 2);
|
||||
this.AssertExecuted("goto_end");
|
||||
this.AssertExecuted("end_all");
|
||||
@@ -90,9 +90,9 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
[Theory]
|
||||
[InlineData(12)]
|
||||
[InlineData(37)]
|
||||
public async Task ConditionAction(int input)
|
||||
public async Task ConditionActionAsync(int input)
|
||||
{
|
||||
await this.RunWorkflow("Condition.yaml", input);
|
||||
await this.RunWorkflowAsync("Condition.yaml", input);
|
||||
this.AssertExecutionCount(expectedCount: 9);
|
||||
this.AssertExecuted("setVariable_test");
|
||||
this.AssertExecuted("conditionGroup_test");
|
||||
@@ -118,9 +118,9 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
[Theory]
|
||||
[InlineData(12, 7)]
|
||||
[InlineData(37, 9)]
|
||||
public async Task ConditionActionWithElse(int input, int expectedActions)
|
||||
public async Task ConditionActionWithElseAsync(int input, int expectedActions)
|
||||
{
|
||||
await this.RunWorkflow("ConditionElse.yaml", input);
|
||||
await this.RunWorkflowAsync("ConditionElse.yaml", input);
|
||||
this.AssertExecutionCount(expectedActions);
|
||||
this.AssertExecuted("setVariable_test");
|
||||
this.AssertExecuted("conditionGroup_test");
|
||||
@@ -149,9 +149,9 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
[InlineData("SetTextVariable.yaml", 1, "set_text")]
|
||||
[InlineData("ClearAllVariables.yaml", 1, "clear_all")]
|
||||
[InlineData("ResetVariable.yaml", 2, "clear_var")]
|
||||
public async Task ExecuteAction(string workflowFile, int expectedCount, string expectedId)
|
||||
public async Task ExecuteActionAsync(string workflowFile, int expectedCount, string expectedId)
|
||||
{
|
||||
await this.RunWorkflow(workflowFile);
|
||||
await this.RunWorkflowAsync(workflowFile);
|
||||
this.AssertExecutionCount(expectedCount);
|
||||
this.AssertExecuted(expectedId);
|
||||
}
|
||||
@@ -240,14 +240,13 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
}
|
||||
}
|
||||
|
||||
private void AssertMessage(string message)
|
||||
{
|
||||
private void AssertMessage(string message) =>
|
||||
Assert.Contains(this.WorkflowEvents.OfType<MessageActivityEvent>(), e => string.Equals(e.Message.Trim(), message, StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
private Task RunWorkflow(string workflowPath) => this.RunWorkflow<string>(workflowPath, string.Empty);
|
||||
private Task RunWorkflowAsync(string workflowPath) =>
|
||||
this.RunWorkflowAsync(workflowPath, string.Empty);
|
||||
|
||||
private async Task RunWorkflow<TInput>(string workflowPath, TInput workflowInput) where TInput : notnull
|
||||
private async Task RunWorkflowAsync<TInput>(string workflowPath, TInput workflowInput) where TInput : notnull
|
||||
{
|
||||
using StreamReader yamlReader = File.OpenText(Path.Combine("Workflows", workflowPath));
|
||||
Mock<WorkflowAgentProvider> mockAgentProvider = new(MockBehavior.Strict);
|
||||
@@ -257,7 +256,7 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
|
||||
StreamingRun run = await InProcessExecution.StreamAsync(workflow, workflowInput);
|
||||
|
||||
this.WorkflowEvents = run.WatchStreamAsync().ToEnumerable().ToImmutableList();
|
||||
this.WorkflowEvents = run.WatchStreamAsync().ToEnumerable().ToList();
|
||||
foreach (WorkflowEvent workflowEvent in this.WorkflowEvents)
|
||||
{
|
||||
if (workflowEvent is ExecutorInvokedEvent invokeEvent)
|
||||
@@ -278,16 +277,14 @@ public sealed class DeclarativeWorkflowTest(ITestOutputHelper output) : Workflow
|
||||
this.Output.WriteLine($"MESSAGE: {messageEvent.Response.Messages[0].Text.Trim()}");
|
||||
}
|
||||
}
|
||||
this.WorkflowEventCounts = this.WorkflowEvents.GroupBy(e => e.GetType()).ToImmutableDictionary(e => e.Key, e => e.Count());
|
||||
this.WorkflowEventCounts = this.WorkflowEvents.GroupBy(e => e.GetType()).ToDictionary(e => e.Key, e => e.Count());
|
||||
}
|
||||
|
||||
private sealed class RootExecutor() :
|
||||
ReflectingExecutor<RootExecutor>(WorkflowActionVisitor.Steps.Root("anything")),
|
||||
IMessageHandler<string>
|
||||
{
|
||||
public async ValueTask HandleAsync(string message, IWorkflowContext context)
|
||||
{
|
||||
await context.SendMessageAsync($"{this.Id}: {DateTime.UtcNow.ToShortTimeString()}").ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask HandleAsync(string message, IWorkflowContext context) =>
|
||||
await context.SendMessageAsync($"{this.Id}: {DateTime.UtcNow:t}").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
+3
-4
@@ -78,8 +78,7 @@ public class FormulaValueExtensionsTests
|
||||
{
|
||||
BlankValue formulaValue = FormulaValue.NewBlank();
|
||||
Assert.Equal(DataType.Blank, formulaValue.GetDataType());
|
||||
|
||||
BlankDataValue dataCopy = Assert.IsType<BlankDataValue>(formulaValue.ToDataValue());
|
||||
Assert.IsType<BlankDataValue>(formulaValue.ToDataValue());
|
||||
|
||||
Assert.Equal(string.Empty, formulaValue.Format());
|
||||
}
|
||||
@@ -89,7 +88,7 @@ public class FormulaValueExtensionsTests
|
||||
{
|
||||
VoidValue formulaValue = FormulaValue.NewVoid();
|
||||
Assert.Equal(DataType.Unspecified, formulaValue.GetDataType());
|
||||
BlankDataValue dataCopy = Assert.IsType<BlankDataValue>(formulaValue.ToDataValue());
|
||||
Assert.IsType<BlankDataValue>(formulaValue.ToDataValue());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -193,7 +192,7 @@ public class FormulaValueExtensionsTests
|
||||
new NamedValue("FieldA", FormulaValue.New("Value1")),
|
||||
new NamedValue("FieldB", FormulaValue.New("Value2")),
|
||||
new NamedValue("FieldC", FormulaValue.New("Value3")));
|
||||
TableValue formulaValue = TableValue.NewTable(recordValue.Type, [recordValue]);
|
||||
TableValue formulaValue = FormulaValue.NewTable(recordValue.Type, [recordValue]);
|
||||
|
||||
TableDataValue dataValue = formulaValue.ToTable();
|
||||
Assert.Equal(formulaValue.Rows.Count(), dataValue.Values.Length);
|
||||
|
||||
+16
-18
@@ -14,23 +14,23 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.Interpreter;
|
||||
public sealed class DeclarativeWorkflowModelTest(ITestOutputHelper output) : WorkflowTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task GetDepthForDefault()
|
||||
public async Task GetDepthForDefaultAsync()
|
||||
{
|
||||
DeclarativeWorkflowModel model = new(this.CreateExecutor("root"));
|
||||
DeclarativeWorkflowModel model = new(CreateExecutor("root"));
|
||||
Assert.Equal(0, model.GetDepth(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task GetDepthForMissingNode()
|
||||
public async Task GetDepthForMissingNodeAsync()
|
||||
{
|
||||
DeclarativeWorkflowModel model = new(this.CreateExecutor("root"));
|
||||
DeclarativeWorkflowModel model = new(CreateExecutor("root"));
|
||||
Assert.Throws<DeclarativeModelException>(() => model.GetDepth("missing"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConnectMissingNode()
|
||||
public async Task ConnectMissingNodeAsync()
|
||||
{
|
||||
TestExecutor rootExecutor = this.CreateExecutor("root");
|
||||
TestExecutor rootExecutor = CreateExecutor("root");
|
||||
DeclarativeWorkflowModel model = new(rootExecutor);
|
||||
model.AddLink("root", "missing");
|
||||
WorkflowBuilder workflowBuilder = new(rootExecutor);
|
||||
@@ -38,36 +38,34 @@ public sealed class DeclarativeWorkflowModelTest(ITestOutputHelper output) : Wor
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddToMissingParent()
|
||||
public async Task AddToMissingParentAsync()
|
||||
{
|
||||
DeclarativeWorkflowModel model = new(this.CreateExecutor("root"));
|
||||
Assert.Throws<DeclarativeModelException>(() => model.AddNode(this.CreateExecutor("next"), "missing"));
|
||||
DeclarativeWorkflowModel model = new(CreateExecutor("root"));
|
||||
Assert.Throws<DeclarativeModelException>(() => model.AddNode(CreateExecutor("next"), "missing"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LinkFromMissingSource()
|
||||
public async Task LinkFromMissingSourceAsync()
|
||||
{
|
||||
DeclarativeWorkflowModel model = new(this.CreateExecutor("root"));
|
||||
DeclarativeWorkflowModel model = new(CreateExecutor("root"));
|
||||
Assert.Throws<DeclarativeModelException>(() => model.AddLink("missing", "anything"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LocateMissingParent()
|
||||
public async Task LocateMissingParentAsync()
|
||||
{
|
||||
DeclarativeWorkflowModel model = new(this.CreateExecutor("root"));
|
||||
DeclarativeWorkflowModel model = new(CreateExecutor("root"));
|
||||
Assert.Null(model.LocateParent<TestExecutor>(null));
|
||||
Assert.Throws<DeclarativeModelException>(() => model.LocateParent<TestExecutor>("missing"));
|
||||
}
|
||||
|
||||
private TestExecutor CreateExecutor(string id) => new(id);
|
||||
private static TestExecutor CreateExecutor(string id) => new(id);
|
||||
|
||||
internal sealed class TestExecutor(string actionId) :
|
||||
ReflectingExecutor<TestExecutor>(actionId),
|
||||
IMessageHandler<string>
|
||||
{
|
||||
public async ValueTask HandleAsync(string message, IWorkflowContext context)
|
||||
{
|
||||
await context.SendMessageAsync($"{this.Id}: {DateTime.UtcNow.ToShortTimeString()}").ConfigureAwait(false);
|
||||
}
|
||||
public async ValueTask HandleAsync(string message, IWorkflowContext context) =>
|
||||
await context.SendMessageAsync($"{this.Id}: {DateTime.UtcNow:t}").ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-11
@@ -14,40 +14,40 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
public sealed class ClearAllVariablesExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task ClearWorkflowScope()
|
||||
public async Task ClearWorkflowScopeAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("NoVar", FormulaValue.New("Old value"));
|
||||
|
||||
ClearAllVariables model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ClearWorkflowScope)),
|
||||
this.FormatDisplayName(nameof(ClearWorkflowScopeAsync)),
|
||||
VariablesToClear.ConversationScopedVariables);
|
||||
|
||||
// Act
|
||||
ClearAllVariablesExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyUndefined("NoVar");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ClearUndefinedScope()
|
||||
public async Task ClearUndefinedScopeAsync()
|
||||
{
|
||||
// Arrange
|
||||
ClearAllVariables model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ClearUndefinedScope)),
|
||||
this.FormatDisplayName(nameof(ClearUndefinedScopeAsync)),
|
||||
VariablesToClear.UserScopedVariables);
|
||||
|
||||
// Act
|
||||
ClearAllVariablesExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyUndefined("NoVar");
|
||||
}
|
||||
|
||||
@@ -61,8 +61,6 @@ public sealed class ClearAllVariablesExecutorTest(ITestOutputHelper output) : Wo
|
||||
Variables = EnumExpression<VariablesToClearWrapper>.Literal(VariablesToClearWrapper.Get(variableTarget)),
|
||||
};
|
||||
|
||||
ClearAllVariables model = this.AssignParent<ClearAllVariables>(actionBuilder);
|
||||
|
||||
return model;
|
||||
return AssignParent<ClearAllVariables>(actionBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
+17
-19
@@ -14,7 +14,7 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
public sealed class ParseValueExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task ParseTable()
|
||||
public async Task ParseTableAsync()
|
||||
{
|
||||
// Arrange
|
||||
RecordDataType.Builder recordBuilder =
|
||||
@@ -27,73 +27,73 @@ public sealed class ParseValueExecutorTest(ITestOutputHelper output) : WorkflowA
|
||||
};
|
||||
ParseValue model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ParseTable)),
|
||||
this.FormatDisplayName(nameof(ParseTableAsync)),
|
||||
recordBuilder,
|
||||
@"{ ""key1"": ""val1"" }");
|
||||
|
||||
// Act
|
||||
ParseValueExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState("Target", FormulaValue.NewRecordFromFields(new NamedValue("key1", FormulaValue.New("val1"))));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParseBoolean()
|
||||
public async Task ParseBooleanAsync()
|
||||
{
|
||||
// Arrange
|
||||
ParseValue model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ParseTable)),
|
||||
this.FormatDisplayName(nameof(ParseTableAsync)),
|
||||
new BooleanDataType.Builder(),
|
||||
"True");
|
||||
|
||||
// Act
|
||||
ParseValueExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState("Target", FormulaValue.New(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParseNumber()
|
||||
public async Task ParseNumberAsync()
|
||||
{
|
||||
// Arrange
|
||||
ParseValue model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ParseNumber)),
|
||||
this.FormatDisplayName(nameof(ParseNumberAsync)),
|
||||
new NumberDataType.Builder(),
|
||||
"42");
|
||||
|
||||
// Act
|
||||
ParseValueExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState("Target", FormulaValue.New(42));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ParseString()
|
||||
public async Task ParseStringAsync()
|
||||
{
|
||||
// Arrange
|
||||
ParseValue model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ParseString)),
|
||||
this.FormatDisplayName(nameof(ParseStringAsync)),
|
||||
new StringDataType.Builder(),
|
||||
"Hello, World!");
|
||||
|
||||
// Act
|
||||
ParseValueExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState("Target", FormulaValue.New("Hello, World!"));
|
||||
}
|
||||
|
||||
@@ -109,8 +109,6 @@ public sealed class ParseValueExecutorTest(ITestOutputHelper output) : WorkflowA
|
||||
Value = new ValueExpression.Builder(ValueExpression.Literal(StringDataValue.Create(sourceText))),
|
||||
};
|
||||
|
||||
ParseValue model = this.AssignParent<ParseValue>(actionBuilder);
|
||||
|
||||
return model;
|
||||
return AssignParent<ParseValue>(actionBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-11
@@ -14,7 +14,7 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
public sealed class ResetVariableExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task ResetDefinedValue()
|
||||
public async Task ResetDefinedValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("MyVar1", FormulaValue.New("Value #1"));
|
||||
@@ -22,36 +22,36 @@ public sealed class ResetVariableExecutorTest(ITestOutputHelper output) : Workfl
|
||||
|
||||
ResetVariable model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ResetDefinedValue)),
|
||||
this.FormatDisplayName(nameof(ResetDefinedValueAsync)),
|
||||
FormatVariablePath("MyVar1"));
|
||||
|
||||
// Act
|
||||
ResetVariableExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyUndefined("MyVar1");
|
||||
this.VerifyState("MyVar2", FormulaValue.New("Value #2"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ResetUndefinedValue()
|
||||
public async Task ResetUndefinedValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("MyVar1", FormulaValue.New("Value #1"));
|
||||
|
||||
ResetVariable model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(ResetUndefinedValue)),
|
||||
this.FormatDisplayName(nameof(ResetUndefinedValueAsync)),
|
||||
FormatVariablePath("NoVar"));
|
||||
|
||||
// Act
|
||||
ResetVariableExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyUndefined("NoVar");
|
||||
this.VerifyState("MyVar1", FormulaValue.New("Value #1"));
|
||||
}
|
||||
@@ -66,8 +66,6 @@ public sealed class ResetVariableExecutorTest(ITestOutputHelper output) : Workfl
|
||||
Variable = InitializablePropertyPath.Create(variablePath),
|
||||
};
|
||||
|
||||
ResetVariable model = this.AssignParent<ResetVariable>(actionBuilder);
|
||||
|
||||
return model;
|
||||
return AssignParent<ResetVariable>(actionBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-7
@@ -13,20 +13,20 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
public sealed class SendActivityExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task CaptureActivity()
|
||||
public async Task CaptureActivityAsync()
|
||||
{
|
||||
// Arrange
|
||||
SendActivity model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(CaptureActivity)),
|
||||
this.FormatDisplayName(nameof(CaptureActivityAsync)),
|
||||
"Test activity message");
|
||||
|
||||
// Act
|
||||
SendActivityExecutor action = new(model, this.State);
|
||||
WorkflowEvent[] events = await this.Execute(action);
|
||||
WorkflowEvent[] events = await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
Assert.Contains(events, e => e is MessageActivityEvent);
|
||||
}
|
||||
|
||||
@@ -46,8 +46,6 @@ public sealed class SendActivityExecutorTest(ITestOutputHelper output) : Workflo
|
||||
Activity = activityBuilder.Build(),
|
||||
};
|
||||
|
||||
SendActivity model = this.AssignParent<SendActivity>(actionBuilder);
|
||||
|
||||
return model;
|
||||
return AssignParent<SendActivity>(actionBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
+9
-11
@@ -14,42 +14,42 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
public sealed class SetTextVariableExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public async Task SetLiteralValue()
|
||||
public async Task SetLiteralValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
SetTextVariable model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(SetLiteralValue)),
|
||||
this.FormatDisplayName(nameof(SetLiteralValueAsync)),
|
||||
FormatVariablePath("TextVar"),
|
||||
"Text variable value");
|
||||
|
||||
// Act
|
||||
SetTextVariableExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState("TextVar", FormulaValue.New("Text variable value"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateExistingValue()
|
||||
public async Task UpdateExistingValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("TextVar", FormulaValue.New("Old value"));
|
||||
|
||||
SetTextVariable model =
|
||||
this.CreateModel(
|
||||
this.FormatDisplayName(nameof(UpdateExistingValue)),
|
||||
this.FormatDisplayName(nameof(UpdateExistingValueAsync)),
|
||||
FormatVariablePath("TextVar"),
|
||||
"New value");
|
||||
|
||||
// Act
|
||||
SetTextVariableExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState("TextVar", FormulaValue.New("New value"));
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ public sealed class SetTextVariableExecutorTest(ITestOutputHelper output) : Work
|
||||
Value = TemplateLine.Parse(textValue),
|
||||
};
|
||||
|
||||
SetTextVariable model = this.AssignParent<SetTextVariable>(actionBuilder);
|
||||
|
||||
return model;
|
||||
return AssignParent<SetTextVariable>(actionBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
+37
-47
@@ -14,147 +14,139 @@ namespace Microsoft.Agents.Workflows.Declarative.UnitTests.ObjectModel;
|
||||
public sealed class SetVariableExecutorTest(ITestOutputHelper output) : WorkflowActionExecutorTest(output)
|
||||
{
|
||||
[Fact]
|
||||
public void InvalidModel()
|
||||
{
|
||||
public void InvalidModel() =>
|
||||
// Arrange, Act, Assert
|
||||
Assert.Throws<DeclarativeModelException>(() => new SetVariableExecutor(new SetVariable(), this.State));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetNumericValue()
|
||||
{
|
||||
public async Task SetNumericValueAsync() =>
|
||||
// Arrange, Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetNumericValue),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetNumericValueAsync),
|
||||
variableName: "TestVariable",
|
||||
variableValue: new NumberDataValue(42),
|
||||
expectedValue: FormulaValue.New(42));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetStringValue()
|
||||
{
|
||||
public async Task SetStringValueAsync() =>
|
||||
// Arrange, Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetStringValue),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetStringValueAsync),
|
||||
variableName: "TestVariable",
|
||||
variableValue: new StringDataValue("Text"),
|
||||
expectedValue: FormulaValue.New("Text"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetBooleanValue()
|
||||
{
|
||||
public async Task SetBooleanValueAsync() =>
|
||||
// Arrange, Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetBooleanValue),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetBooleanValueAsync),
|
||||
variableName: "TestVariable",
|
||||
variableValue: new BooleanDataValue(true),
|
||||
expectedValue: FormulaValue.New(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetBooleanExpression()
|
||||
public async Task SetBooleanExpressionAsync()
|
||||
{
|
||||
// Arrange
|
||||
ValueExpression.Builder expressionBuilder = new(ValueExpression.Expression("true || false"));
|
||||
|
||||
// Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetBooleanExpression),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetBooleanExpressionAsync),
|
||||
variableName: "TestVariable",
|
||||
valueExpression: expressionBuilder,
|
||||
expectedValue: FormulaValue.New(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetNumberExpression()
|
||||
public async Task SetNumberExpressionAsync()
|
||||
{
|
||||
// Arrange
|
||||
ValueExpression.Builder expressionBuilder = new(ValueExpression.Expression("9 - 3"));
|
||||
|
||||
// Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetBooleanExpression),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetBooleanExpressionAsync),
|
||||
variableName: "TestVariable",
|
||||
valueExpression: expressionBuilder,
|
||||
expectedValue: FormulaValue.New(6));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetStringExpression()
|
||||
public async Task SetStringExpressionAsync()
|
||||
{
|
||||
// Arrange
|
||||
ValueExpression.Builder expressionBuilder = new(ValueExpression.Expression(@"Concatenate(""A"", ""B"", ""C"")"));
|
||||
|
||||
// Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetBooleanExpression),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetBooleanExpressionAsync),
|
||||
variableName: "TestVariable",
|
||||
valueExpression: expressionBuilder,
|
||||
expectedValue: FormulaValue.New("ABC"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetBooleanVariable()
|
||||
public async Task SetBooleanVariableAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("Source", FormulaValue.New(true));
|
||||
ValueExpression.Builder expressionBuilder = new(ValueExpression.Variable(PropertyPath.TopicVariable("Source")));
|
||||
|
||||
// Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetBooleanExpression),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetBooleanExpressionAsync),
|
||||
variableName: "TestVariable",
|
||||
valueExpression: expressionBuilder,
|
||||
expectedValue: FormulaValue.New(true));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetNumberVariable()
|
||||
public async Task SetNumberVariableAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("Source", FormulaValue.New(321));
|
||||
ValueExpression.Builder expressionBuilder = new(ValueExpression.Variable(PropertyPath.TopicVariable("Source")));
|
||||
|
||||
// Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetBooleanExpression),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetBooleanExpressionAsync),
|
||||
variableName: "TestVariable",
|
||||
valueExpression: expressionBuilder,
|
||||
expectedValue: FormulaValue.New(321));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SetStringVariable()
|
||||
public async Task SetStringVariableAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("Source", FormulaValue.New("Test"));
|
||||
ValueExpression.Builder expressionBuilder = new(ValueExpression.Variable(PropertyPath.TopicVariable("Source")));
|
||||
|
||||
// Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(SetBooleanExpression),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(SetBooleanExpressionAsync),
|
||||
variableName: "TestVariable",
|
||||
valueExpression: expressionBuilder,
|
||||
expectedValue: FormulaValue.New("Test"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateExistingValue()
|
||||
public async Task UpdateExistingValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
this.State.Set("VarA", FormulaValue.New(33));
|
||||
|
||||
// Act, Assert
|
||||
await this.ExecuteTest(
|
||||
displayName: nameof(UpdateExistingValue),
|
||||
await this.ExecuteTestAsync(
|
||||
displayName: nameof(UpdateExistingValueAsync),
|
||||
variableName: "VarA",
|
||||
variableValue: new NumberDataValue(42),
|
||||
expectedValue: FormulaValue.New(42));
|
||||
}
|
||||
|
||||
private Task ExecuteTest(
|
||||
private Task ExecuteTestAsync(
|
||||
string displayName,
|
||||
string variableName,
|
||||
DataValue variableValue,
|
||||
@@ -164,10 +156,10 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow
|
||||
ValueExpression.Builder expressionBuilder = new(ValueExpression.Literal(variableValue));
|
||||
|
||||
// Act & Assert
|
||||
return this.ExecuteTest(displayName, variableName, expressionBuilder, expectedValue);
|
||||
return this.ExecuteTestAsync(displayName, variableName, expressionBuilder, expectedValue);
|
||||
}
|
||||
|
||||
private async Task ExecuteTest(
|
||||
private async Task ExecuteTestAsync(
|
||||
string displayName,
|
||||
string variableName,
|
||||
ValueExpression.Builder valueExpression,
|
||||
@@ -184,10 +176,10 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow
|
||||
|
||||
// Act
|
||||
SetVariableExecutor action = new(model, this.State);
|
||||
await this.Execute(action);
|
||||
await this.ExecuteAsync(action);
|
||||
|
||||
// Assert
|
||||
this.VerifyModel(model, action);
|
||||
VerifyModel(model, action);
|
||||
this.VerifyState(variableName, expectedValue);
|
||||
}
|
||||
|
||||
@@ -202,8 +194,6 @@ public sealed class SetVariableExecutorTest(ITestOutputHelper output) : Workflow
|
||||
Value = valueExpression,
|
||||
};
|
||||
|
||||
SetVariable model = this.AssignParent<SetVariable>(actionBuilder);
|
||||
|
||||
return model;
|
||||
return AssignParent<SetVariable>(actionBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
+5
-9
@@ -24,7 +24,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
|
||||
protected string FormatDisplayName(string name) => $"{this.GetType().Name}_{name}";
|
||||
|
||||
internal async Task<WorkflowEvent[]> Execute(DeclarativeActionExecutor executor)
|
||||
internal async Task<WorkflowEvent[]> ExecuteAsync(DeclarativeActionExecutor executor)
|
||||
{
|
||||
TestWorkflowExecutor workflowExecutor = new();
|
||||
WorkflowBuilder workflowBuilder = new(workflowExecutor);
|
||||
@@ -36,7 +36,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
return events;
|
||||
}
|
||||
|
||||
internal void VerifyModel(DialogAction model, DeclarativeActionExecutor action)
|
||||
internal static void VerifyModel(DialogAction model, DeclarativeActionExecutor action)
|
||||
{
|
||||
Assert.Equal(model.Id, action.Id);
|
||||
Assert.Equal(model, action.Model);
|
||||
@@ -52,12 +52,10 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
|
||||
protected void VerifyUndefined(string variableName) => this.VerifyUndefined(variableName, VariableScopeNames.Topic);
|
||||
|
||||
internal void VerifyUndefined(string variableName, string scopeName)
|
||||
{
|
||||
internal void VerifyUndefined(string variableName, string scopeName) =>
|
||||
Assert.IsType<BlankValue>(this.State.Get(variableName, scopeName));
|
||||
}
|
||||
|
||||
protected TAction AssignParent<TAction>(DialogAction.Builder actionBuilder) where TAction : DialogAction
|
||||
protected static TAction AssignParent<TAction>(DialogAction.Builder actionBuilder) where TAction : DialogAction
|
||||
{
|
||||
OnActivity.Builder activityBuilder =
|
||||
new()
|
||||
@@ -76,9 +74,7 @@ public abstract class WorkflowActionExecutorTest(ITestOutputHelper output) : Wor
|
||||
ReflectingExecutor<TestWorkflowExecutor>(nameof(TestWorkflowExecutor)),
|
||||
IMessageHandler<WorkflowFormulaState>
|
||||
{
|
||||
public async ValueTask HandleAsync(WorkflowFormulaState message, IWorkflowContext context)
|
||||
{
|
||||
public async ValueTask HandleAsync(WorkflowFormulaState message, IWorkflowContext context) =>
|
||||
await context.SendMessageAsync(new ExecutorResultMessage(this.Id)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -71,7 +71,7 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes
|
||||
public void FormatTextSegment()
|
||||
{
|
||||
// Arrange
|
||||
TemplateSegment textSegment = TextSegment.FromText("Hello World");
|
||||
TemplateSegment textSegment = TemplateSegment.FromText("Hello World");
|
||||
TemplateLine line = new([textSegment]);
|
||||
|
||||
// Act
|
||||
@@ -125,7 +125,7 @@ public class TemplateExtensionsTests(ITestOutputHelper output) : RecalcEngineTes
|
||||
public void FormatMultipleSegments()
|
||||
{
|
||||
// Arrange
|
||||
TemplateSegment textSegment = TextSegment.FromText("Hello ");
|
||||
TemplateSegment textSegment = TemplateSegment.FromText("Hello ");
|
||||
ExpressionSegment expressionSegment = new(ValueExpression.Expression(@"""World"""));
|
||||
TemplateLine line = new([textSegment, expressionSegment]);
|
||||
|
||||
|
||||
+55
-137
@@ -47,36 +47,28 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
#region BoolExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void BoolExpressionGetValueForNull()
|
||||
{
|
||||
public void BoolExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<ArgumentNullException>((BoolExpression)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolExpressionGetValueForInvalid()
|
||||
{
|
||||
public void BoolExpressionGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<InvalidExpressionOutputTypeException>(BoolExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolExpressionGetValueForLiteral()
|
||||
{
|
||||
public void BoolExpressionGetValueForLiteral() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
BoolExpression.Literal(true),
|
||||
expectedValue: true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolExpressionGetValueForBlank()
|
||||
{
|
||||
public void BoolExpressionGetValueForBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
BoolExpression.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: false);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolExpressionGetValueForVariable()
|
||||
@@ -88,49 +80,39 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BoolExpressionGetValueForFormula()
|
||||
{
|
||||
public void BoolExpressionGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
BoolExpression.Expression("true || false"),
|
||||
expectedValue: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region StringExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void StringExpressionGetValueForNull()
|
||||
{
|
||||
public void StringExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<ArgumentNullException>((StringExpression)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringExpressionGetValueForInvalid()
|
||||
{
|
||||
public void StringExpressionGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<InvalidExpressionOutputTypeException>(StringExpression.Variable(PropertyPath.TopicVariable(Variables.BoolValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringExpressionGetValueForStringExpressionBlank()
|
||||
{
|
||||
public void StringExpressionGetValueForStringExpressionBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
StringExpression.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: string.Empty);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringExpressionGetValueForLiteral()
|
||||
{
|
||||
public void StringExpressionGetValueForLiteral() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
StringExpression.Literal("test"),
|
||||
expectedValue: "test");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringExpressionGetValueForVariable()
|
||||
@@ -142,13 +124,11 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringExpressionGetValueForFormula()
|
||||
{
|
||||
public void StringExpressionGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
StringExpression.Expression(@"""A"" & ""B"""),
|
||||
expectedValue: "AB");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StringExpressionGetValueForRecord()
|
||||
@@ -173,36 +153,28 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
#region IntExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void IntExpressionGetValueForNull()
|
||||
{
|
||||
public void IntExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<ArgumentNullException>((IntExpression)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntExpressionGetValueForInvalid()
|
||||
{
|
||||
public void IntExpressionGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<InvalidExpressionOutputTypeException>(IntExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntExpressionGetValueForIntExpressionBlank()
|
||||
{
|
||||
public void IntExpressionGetValueForIntExpressionBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
IntExpression.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntExpressionGetValueForLiteral()
|
||||
{
|
||||
public void IntExpressionGetValueForLiteral() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
IntExpression.Literal(7),
|
||||
expectedValue: 7);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntExpressionGetValueForVariable()
|
||||
@@ -214,96 +186,76 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IntExpressionGetValueForFormula()
|
||||
{
|
||||
public void IntExpressionGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
IntExpression.Expression("1 + 6"),
|
||||
expectedValue: 7);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region NumberExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void NumberExpressionGetValueForNull()
|
||||
{
|
||||
public void NumberExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<ArgumentNullException>((NumberExpression)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberExpressionGetValueForInvalid()
|
||||
{
|
||||
public void NumberExpressionGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<InvalidExpressionOutputTypeException>(NumberExpression.Variable(PropertyPath.TopicVariable(Variables.StringValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberExpressionGetValueForBlank()
|
||||
{
|
||||
public void NumberExpressionGetValueForBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
NumberExpression.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberExpressionGetValueForLiteral()
|
||||
{
|
||||
public void NumberExpressionGetValueForLiteral() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
NumberExpression.Literal(3.14),
|
||||
expectedValue: 3.14);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberExpressionGetValueForVariable()
|
||||
{
|
||||
public void NumberExpressionGetValueForVariable() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
NumberExpression.Variable(PropertyPath.TopicVariable(Variables.NumberValue)),
|
||||
expectedValue: 33.3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumberExpressionGetValueForFormula()
|
||||
{
|
||||
public void NumberExpressionGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
NumberExpression.Expression("31.1 + 2.2"),
|
||||
expectedValue: 33.3);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DataValueExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void DataValueExpressionGetValueForNull()
|
||||
{
|
||||
public void DataValueExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<ArgumentNullException>((ValueExpression)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataValueExpressionGetValueForDataValueExpressionBlank()
|
||||
{
|
||||
public void DataValueExpressionGetValueForDataValueExpressionBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ValueExpression.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: DataValue.Blank());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataValueExpressionGetValueForLiteral()
|
||||
{
|
||||
public void DataValueExpressionGetValueForLiteral() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ValueExpression.Literal(DataValue.Create("test")),
|
||||
expectedValue: DataValue.Create("test"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataValueExpressionGetValueForVariable()
|
||||
@@ -315,85 +267,69 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DataValueExpressionGetValueForFormula()
|
||||
{
|
||||
public void DataValueExpressionGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ValueExpression.Expression(@"""A"" & ""B"""),
|
||||
expectedValue: DataValue.Create("AB"));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region EnumExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void EnumExpressionGetValueForNull()
|
||||
{
|
||||
public void EnumExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<VariablesToClearWrapper, ArgumentNullException>((EnumExpression<VariablesToClearWrapper>)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumExpressionGetValueForInvalid()
|
||||
{
|
||||
public void EnumExpressionGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<VariablesToClearWrapper, InvalidExpressionOutputTypeException>(EnumExpression<VariablesToClearWrapper>.Variable(PropertyPath.TopicVariable(Variables.BoolValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumExpressionGetValueForLiteral()
|
||||
{
|
||||
public void EnumExpressionGetValueForLiteral() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression<VariablesToClearWrapper>(
|
||||
this.EvaluateExpression(
|
||||
EnumExpression<VariablesToClearWrapper>.Literal(VariablesToClearWrapper.Get(VariablesToClear.ConversationScopedVariables)),
|
||||
expectedValue: VariablesToClear.ConversationScopedVariables);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumExpressionGetValueForBlank()
|
||||
{
|
||||
public void EnumExpressionGetValueForBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression<VariablesToClearWrapper>(
|
||||
this.EvaluateExpression(
|
||||
EnumExpression<VariablesToClearWrapper>.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: VariablesToClear.ConversationScopedVariables);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumExpressionGetValueForVariable()
|
||||
{
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression<VariablesToClearWrapper>(
|
||||
this.EvaluateExpression(
|
||||
EnumExpression<VariablesToClearWrapper>.Variable(PropertyPath.TopicVariable(Variables.EnumValue)),
|
||||
expectedValue: VariablesToClear.ConversationScopedVariables);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EnumExpressionGetValueForFormula()
|
||||
{
|
||||
public void EnumExpressionGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression<VariablesToClearWrapper>(
|
||||
this.EvaluateExpression(
|
||||
EnumExpression<VariablesToClearWrapper>.Expression(@"""ConversationScoped"" & ""Variables"""),
|
||||
expectedValue: VariablesToClear.ConversationScopedVariables);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ObjectExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void ObjectExpressionGetValueForNull()
|
||||
{
|
||||
public void ObjectExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<RecordDataValue, ArgumentNullException>((ObjectExpression<RecordDataValue>)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectExpressionGetValueForInvalid()
|
||||
{
|
||||
public void ObjectExpressionGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<RecordDataValue, InvalidExpressionOutputTypeException>(ObjectExpression<RecordDataValue>.Variable(PropertyPath.TopicVariable(Variables.BoolValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectExpressionGetValueForLiteral()
|
||||
@@ -402,20 +338,18 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
RecordDataValue.Builder recordBuilder = new();
|
||||
recordBuilder.Properties.Add(nameof(EnvironmentVariableReference.SchemaName), new StringDataValue("test"));
|
||||
RecordDataValue objectRecord = recordBuilder.Build();
|
||||
EnvironmentVariableReference element = new EnvironmentVariableReference.Builder() { SchemaName = "test" }.Build();
|
||||
_ = new EnvironmentVariableReference.Builder() { SchemaName = "test" }.Build();
|
||||
this.EvaluateExpression(
|
||||
ObjectExpression<RecordDataValue>.Literal(objectRecord),
|
||||
expectedValue: objectRecord);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectExpressionGetValueForBlank()
|
||||
{
|
||||
public void ObjectExpressionGetValueForBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ObjectExpression<RecordDataValue>.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: null);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ObjectExpressionGetValueForVariable()
|
||||
@@ -431,18 +365,14 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
#region ArrayExpression Tests
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionGetValueForNull()
|
||||
{
|
||||
public void ArrayExpressionGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<string, ArgumentNullException>((ArrayExpression<string>)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionGetValueForInvalid()
|
||||
{
|
||||
public void ArrayExpressionGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<string, InvalidExpressionOutputTypeException>(ArrayExpression<string>.Variable(PropertyPath.TopicVariable(Variables.BoolValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionGetValueForLiteral()
|
||||
@@ -455,13 +385,11 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionGetValueForBlank()
|
||||
{
|
||||
public void ArrayExpressionGetValueForBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ArrayExpression<string>.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: []);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionGetValueForVariable()
|
||||
@@ -473,40 +401,32 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionGetValueForFormula()
|
||||
{
|
||||
public void ArrayExpressionGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ArrayExpression<string>.Expression(@"[""a"", ""b""]"),
|
||||
expectedValue: ["a", "b"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ArrayExpressionOnly Tests
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionOnlyGetValueForNull()
|
||||
{
|
||||
public void ArrayExpressionOnlyGetValueForNull() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<string, ArgumentNullException>((ArrayExpressionOnly<string>)null!);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionOnlyGetValueForInvalid()
|
||||
{
|
||||
public void ArrayExpressionOnlyGetValueForInvalid() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateInvalidExpression<string, InvalidExpressionOutputTypeException>(ArrayExpressionOnly<string>.Variable(PropertyPath.TopicVariable(Variables.BoolValue)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionOnlyGetValueForBlank()
|
||||
{
|
||||
public void ArrayExpressionOnlyGetValueForBlank() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ArrayExpressionOnly<string>.Variable(PropertyPath.TopicVariable(Variables.BlankValue)),
|
||||
expectedValue: []);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionOnlyGetValueForVariable()
|
||||
@@ -518,13 +438,11 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ArrayExpressionOnlyGetValueForFormula()
|
||||
{
|
||||
public void ArrayExpressionOnlyGetValueForFormula() =>
|
||||
// Arrange, Act & Assert
|
||||
this.EvaluateExpression(
|
||||
ArrayExpressionOnly<string>.Expression(@"[""a"", ""b""]"),
|
||||
expectedValue: ["a", "b"]);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
@@ -565,35 +483,35 @@ public class WorkflowExpressionEngineTests : RecalcEngineTest
|
||||
|
||||
private EvaluationResult<TEnum> EvaluateExpression<TEnum>(EnumExpression<TEnum> expression, TEnum expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
|
||||
where TEnum : EnumWrapper
|
||||
=> this.EvaluateExpression((evaluator) => evaluator.GetValue<TEnum>(expression), expectedValue, expectedSensitivity);
|
||||
=> this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity);
|
||||
|
||||
private void EvaluateInvalidExpression<TEnum, TException>(EnumExpression<TEnum> expression)
|
||||
where TException : Exception
|
||||
where TEnum : EnumWrapper
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TEnum>(expression));
|
||||
where TException : Exception
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
|
||||
|
||||
private EvaluationResult<TValue?> EvaluateExpression<TValue>(ObjectExpression<TValue> expression, TValue? expectedValue, SensitivityLevel expectedSensitivity = SensitivityLevel.None)
|
||||
where TValue : BotElement
|
||||
=> this.EvaluateExpression((evaluator) => evaluator.GetValue<TValue>(expression), expectedValue, expectedSensitivity);
|
||||
=> this.EvaluateExpression((evaluator) => evaluator.GetValue(expression), expectedValue, expectedSensitivity);
|
||||
|
||||
private void EvaluateInvalidExpression<TValue, TException>(ObjectExpression<TValue> expression)
|
||||
where TException : Exception
|
||||
where TValue : BotElement
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression));
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
|
||||
|
||||
private ImmutableArray<TValue> EvaluateExpression<TValue>(ArrayExpression<TValue> expression, TValue[] expectedValue)
|
||||
=> this.EvaluateArrayExpression((evaluator) => evaluator.GetValue<TValue>(expression), expectedValue);
|
||||
=> this.EvaluateArrayExpression((evaluator) => evaluator.GetValue(expression), expectedValue);
|
||||
|
||||
private void EvaluateInvalidExpression<TValue, TException>(ArrayExpression<TValue> expression)
|
||||
where TException : Exception
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression));
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
|
||||
|
||||
private ImmutableArray<TValue> EvaluateExpression<TValue>(ArrayExpressionOnly<TValue> expression, TValue[] expectedValue)
|
||||
=> this.EvaluateArrayExpression((evaluator) => evaluator.GetValue<TValue>(expression), expectedValue);
|
||||
=> this.EvaluateArrayExpression((evaluator) => evaluator.GetValue(expression), expectedValue);
|
||||
|
||||
private void EvaluateInvalidExpression<TValue, TException>(ArrayExpressionOnly<TValue> expression)
|
||||
where TException : Exception
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue<TValue>(expression));
|
||||
=> this.EvaluateInvalidExpression<TException>((evaluator) => evaluator.GetValue(expression));
|
||||
|
||||
private EvaluationResult<TValue> EvaluateExpression<TValue>(
|
||||
Func<WorkflowExpressionEngine, EvaluationResult<TValue>> evaluator,
|
||||
|
||||
@@ -16,7 +16,7 @@ public abstract class WorkflowTest : IDisposable
|
||||
protected WorkflowTest(ITestOutputHelper output)
|
||||
{
|
||||
this.Output = new TestOutputAdapter(output);
|
||||
System.Console.SetOut(this.Output);
|
||||
Console.SetOut(this.Output);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -20,15 +20,14 @@ internal static class TextMessageStreamingExtensions
|
||||
string[] splits = message.Split(' ');
|
||||
for (int i = 0; i < splits.Length - 1; i++)
|
||||
{
|
||||
splits[i] = splits[i] + ' ';
|
||||
splits[i] += " ";
|
||||
}
|
||||
|
||||
return splits.Select(text => (AIContent)new TextContent(text) { RawRepresentation = text });
|
||||
}
|
||||
|
||||
public static AgentRunResponseUpdate ToResponseUpdate(this AIContent content, string? messageId = null, DateTimeOffset? createdAt = null, string? responseId = null, string? agentId = null, string? authorName = null)
|
||||
{
|
||||
return new AgentRunResponseUpdate()
|
||||
public static AgentRunResponseUpdate ToResponseUpdate(this AIContent content, string? messageId = null, DateTimeOffset? createdAt = null, string? responseId = null, string? agentId = null, string? authorName = null) =>
|
||||
new()
|
||||
{
|
||||
Role = ChatRole.Assistant,
|
||||
CreatedAt = createdAt ?? DateTimeOffset.Now,
|
||||
@@ -38,7 +37,6 @@ internal static class TextMessageStreamingExtensions
|
||||
AuthorName = authorName,
|
||||
Contents = [content],
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<AgentRunResponseUpdate> ToAgentRunStream(this string message, DateTimeOffset? createdAt = null, string? messageId = null, string? responseId = null, string? agentId = null, string? authorName = null)
|
||||
{
|
||||
@@ -48,16 +46,14 @@ internal static class TextMessageStreamingExtensions
|
||||
return contents.Select(content => content.ToResponseUpdate(messageId, createdAt, responseId, agentId, authorName));
|
||||
}
|
||||
|
||||
public static ChatMessage ToChatMessage(this IEnumerable<AIContent> contents, string? messageId = null, DateTimeOffset? createdAt = null, string? responseId = null, string? agentId = null, string? authorName = null, string? rawRepresentation = null)
|
||||
{
|
||||
return new ChatMessage(ChatRole.Assistant, contents is List<AIContent> contentsList ? contentsList : contents.ToList())
|
||||
public static ChatMessage ToChatMessage(this IEnumerable<AIContent> contents, string? messageId = null, DateTimeOffset? createdAt = null, string? responseId = null, string? agentId = null, string? authorName = null, string? rawRepresentation = null) =>
|
||||
new(ChatRole.Assistant, contents is List<AIContent> contentsList ? contentsList : contents.ToList())
|
||||
{
|
||||
AuthorName = authorName,
|
||||
CreatedAt = createdAt ?? DateTimeOffset.Now,
|
||||
MessageId = messageId ?? Guid.NewGuid().ToString("N"),
|
||||
RawRepresentation = rawRepresentation,
|
||||
};
|
||||
}
|
||||
|
||||
public static IEnumerable<AgentRunResponseUpdate> StreamMessage(this ChatMessage message, string? responseId = null, string? agentId = null)
|
||||
{
|
||||
@@ -67,10 +63,8 @@ internal static class TextMessageStreamingExtensions
|
||||
return message.Contents.Select(content => content.ToResponseUpdate(messageId, message.CreatedAt, responseId: responseId, agentId: agentId, authorName: message.AuthorName));
|
||||
}
|
||||
|
||||
public static IEnumerable<AgentRunResponseUpdate> StreamMessages(this List<ChatMessage> messages, string? agentId = null)
|
||||
{
|
||||
return messages.SelectMany(message => message.StreamMessage(agentId));
|
||||
}
|
||||
public static IEnumerable<AgentRunResponseUpdate> StreamMessages(this List<ChatMessage> messages, string? agentId = null) =>
|
||||
messages.SelectMany(message => message.StreamMessage(agentId));
|
||||
|
||||
public static List<ChatMessage> ToChatMessages(this IEnumerable<string> messages, string? authorName = null)
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ public class EdgeMapSmokeTests
|
||||
runContext.Executors["executor2"] = new ForwardMessageExecutor<string>("executor2");
|
||||
runContext.Executors["executor3"] = new ForwardMessageExecutor<string>("executor3");
|
||||
|
||||
Dictionary<string, HashSet<Edge>> workflowEdges = new();
|
||||
Dictionary<string, HashSet<Edge>> workflowEdges = [];
|
||||
|
||||
FanInEdgeData edgeData = new(["executor1", "executor2"], "executor3", new EdgeId(0));
|
||||
Edge fanInEdge = new(edgeData);
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Microsoft.Agents.Workflows.UnitTests;
|
||||
|
||||
public class EdgeRunnerTests
|
||||
{
|
||||
private async Task CreateAndRunDirectedEdgeTestAsync(bool? conditionMatch = null, bool? targetMatch = null)
|
||||
private static async Task CreateAndRunDirectedEdgeTestAsync(bool? conditionMatch = null, bool? targetMatch = null)
|
||||
{
|
||||
const string MessageVariant1 = "test";
|
||||
const string MessageVariant2 = "something else";
|
||||
@@ -58,21 +58,21 @@ public class EdgeRunnerTests
|
||||
// NoCondition vs Condition(=> true) vs Condition(=> false)
|
||||
// Untargeted vs Targeted(matching) vs Targeted(not matching)
|
||||
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(); // NoCondition, Untargeted
|
||||
await CreateAndRunDirectedEdgeTestAsync(); // NoCondition, Untargeted
|
||||
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(targetMatch: true); // NoCondition, Targeted
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(targetMatch: false); // NoCondition, Targeted(not matching)
|
||||
await CreateAndRunDirectedEdgeTestAsync(targetMatch: true); // NoCondition, Targeted
|
||||
await CreateAndRunDirectedEdgeTestAsync(targetMatch: false); // NoCondition, Targeted(not matching)
|
||||
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(conditionMatch: true); // Condition(=> true), Untargeted
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(conditionMatch: false); // Condition(=> false), Untargeted
|
||||
await CreateAndRunDirectedEdgeTestAsync(conditionMatch: true); // Condition(=> true), Untargeted
|
||||
await CreateAndRunDirectedEdgeTestAsync(conditionMatch: false); // Condition(=> false), Untargeted
|
||||
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(conditionMatch: true, targetMatch: true); // Condition(=> true), Targeted(matching)
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(conditionMatch: true, targetMatch: false); // Condition(=> true), Targeted(not matching)
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(conditionMatch: false, targetMatch: true); // Condition(=> false), Targeted(matching)
|
||||
await this.CreateAndRunDirectedEdgeTestAsync(conditionMatch: false, targetMatch: false); // Condition(=> false), Targeted(not matching)
|
||||
await CreateAndRunDirectedEdgeTestAsync(conditionMatch: true, targetMatch: true); // Condition(=> true), Targeted(matching)
|
||||
await CreateAndRunDirectedEdgeTestAsync(conditionMatch: true, targetMatch: false); // Condition(=> true), Targeted(not matching)
|
||||
await CreateAndRunDirectedEdgeTestAsync(conditionMatch: false, targetMatch: true); // Condition(=> false), Targeted(matching)
|
||||
await CreateAndRunDirectedEdgeTestAsync(conditionMatch: false, targetMatch: false); // Condition(=> false), Targeted(not matching)
|
||||
}
|
||||
|
||||
private async Task CreateAndRunFanOutEdgeTestAsync(bool? assignerSelectsEmpty = null, bool? targetMatch = null)
|
||||
private static async Task CreateAndRunFanOutEdgeTestAsync(bool? assignerSelectsEmpty = null, bool? targetMatch = null)
|
||||
{
|
||||
TestRunContext runContext = new();
|
||||
|
||||
@@ -122,18 +122,18 @@ public class EdgeRunnerTests
|
||||
// NoAssigned vs Assigner(includes output) vs Assigner(does not include output)
|
||||
// Untargeted vs Targeted(matching) vs Targeted(not matching)
|
||||
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(); // NoAssigner, Untargeted
|
||||
await CreateAndRunFanOutEdgeTestAsync(); // NoAssigner, Untargeted
|
||||
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(targetMatch: true); // NoAssigner, Targeted(matching)
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(targetMatch: false); // NoAssigner, Targeted(not matching)
|
||||
await CreateAndRunFanOutEdgeTestAsync(targetMatch: true); // NoAssigner, Targeted(matching)
|
||||
await CreateAndRunFanOutEdgeTestAsync(targetMatch: false); // NoAssigner, Targeted(not matching)
|
||||
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: false); // Assigner(includes output), Untargeted
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: true); // Assigner(does not include output), Untargeted
|
||||
await CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: false); // Assigner(includes output), Untargeted
|
||||
await CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: true); // Assigner(does not include output), Untargeted
|
||||
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: false, targetMatch: true); // Assigner(includes output), Targeted(matching)
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: false, targetMatch: false); // Assigner(includes output), Targeted(not matching)
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: true, targetMatch: true); // Assigner(does not include output), Targeted(matching)
|
||||
await this.CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: true, targetMatch: false); // Assigner(does not include output), Targeted(not matching)
|
||||
await CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: false, targetMatch: true); // Assigner(includes output), Targeted(matching)
|
||||
await CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: false, targetMatch: false); // Assigner(includes output), Targeted(not matching)
|
||||
await CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: true, targetMatch: true); // Assigner(does not include output), Targeted(matching)
|
||||
await CreateAndRunFanOutEdgeTestAsync(assignerSelectsEmpty: true, targetMatch: false); // Assigner(does not include output), Targeted(not matching)
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -154,13 +154,13 @@ public class EdgeRunnerTests
|
||||
// Step 4: Send message from executor2, should forward now.
|
||||
|
||||
FanInEdgeState state = runner.CreateState();
|
||||
await RunIteration();
|
||||
await RunIterationAsync();
|
||||
|
||||
// Repeat the same sequence, to ensure state is properly reset inside of FanInEdgeState.
|
||||
runContext.QueuedMessages.Clear();
|
||||
await RunIteration();
|
||||
await RunIterationAsync();
|
||||
|
||||
async ValueTask RunIteration()
|
||||
async ValueTask RunIterationAsync()
|
||||
{
|
||||
await runner.ChaseAsync("executor1", new("part1"), state, tracer: null);
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ namespace Microsoft.Agents.Workflows.UnitTests;
|
||||
|
||||
internal sealed class ForwardMessageExecutor<TMessage>(string? id = null) : Executor(id) where TMessage : notnull
|
||||
{
|
||||
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder)
|
||||
{
|
||||
return routeBuilder.AddHandler<TMessage>((message, ctx) => ctx.SendMessageAsync(message));
|
||||
}
|
||||
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
||||
routeBuilder.AddHandler<TMessage>((message, ctx) => ctx.SendMessageAsync(message));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Microsoft.Agents.Workflows.UnitTests;
|
||||
|
||||
internal sealed class InMemoryJsonStore : JsonCheckpointStore
|
||||
{
|
||||
private readonly Dictionary<string, RunCheckpointCache<JsonElement>> _store = new();
|
||||
private readonly Dictionary<string, RunCheckpointCache<JsonElement>> _store = [];
|
||||
|
||||
private RunCheckpointCache<JsonElement> EnsureRunStore(string runId)
|
||||
{
|
||||
|
||||
@@ -33,12 +33,12 @@ public class InProcessStateTests
|
||||
|
||||
for (int i = 0; i < stateActions.Length; i++)
|
||||
{
|
||||
result[i] = CreateWrapperAsync(stateActions[i]);
|
||||
result[i] = CreateWrapper(stateActions[i]);
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
Func<TurnToken, IWorkflowContext, CancellationToken, ValueTask<TurnToken>> CreateWrapperAsync(Func<TState?, TState?> action)
|
||||
Func<TurnToken, IWorkflowContext, CancellationToken, ValueTask<TurnToken>> CreateWrapper(Func<TState?, TState?> action)
|
||||
{
|
||||
return
|
||||
async (turn, context, cancellation) =>
|
||||
@@ -68,7 +68,7 @@ public class InProcessStateTests
|
||||
=> currState => currState.HasValue ? currState + 1 : defaultValue;
|
||||
|
||||
private static Func<int?, int?> ValidateState(int expectedValue, string? because = null, params object[] becauseArgs)
|
||||
=> (int? currState) =>
|
||||
=> currState =>
|
||||
{
|
||||
currState.Should().Be(expectedValue, because, becauseArgs);
|
||||
|
||||
@@ -76,7 +76,7 @@ public class InProcessStateTests
|
||||
};
|
||||
|
||||
private static Func<object?, bool> MaxTurns(int maxTurns)
|
||||
=> (object? maybeTurn) => maybeTurn is not TurnToken turn || turn.Count < maxTurns;
|
||||
=> maybeTurn => maybeTurn is not TurnToken turn || turn.Count < maxTurns;
|
||||
|
||||
[Fact]
|
||||
public async Task InProcessRun_StateShouldPersist_NotCheckpointedAsync()
|
||||
|
||||
@@ -28,7 +28,7 @@ public class JsonSerializationTests
|
||||
}
|
||||
}
|
||||
|
||||
private static int s_nextEdgeId = 0;
|
||||
private static int s_nextEdgeId;
|
||||
|
||||
private static EdgeId TakeEdgeId() => new(Interlocked.Increment(ref s_nextEdgeId));
|
||||
|
||||
@@ -36,14 +36,14 @@ public class JsonSerializationTests
|
||||
{
|
||||
JsonMarshaller marshaller = new(externalOptions);
|
||||
|
||||
JsonElement element = marshaller.Marshal<T>(value);
|
||||
JsonElement element = marshaller.Marshal(value);
|
||||
T deserialized = marshaller.Marshal<T>(element);
|
||||
|
||||
if (deserialized != null)
|
||||
if (deserialized is not null)
|
||||
{
|
||||
if (predicate != null)
|
||||
if (predicate is not null)
|
||||
{
|
||||
deserialized.Should().Match<T>(predicate);
|
||||
deserialized.Should().Match(predicate);
|
||||
}
|
||||
|
||||
return deserialized;
|
||||
@@ -56,7 +56,7 @@ public class JsonSerializationTests
|
||||
[Fact]
|
||||
public void Test_EdgeConnection_JsonRoundtrip()
|
||||
{
|
||||
EdgeConnection connection = new(new List<string> { "Source1", "Source2" }, new List<string> { "Sink1", "Sink2" });
|
||||
EdgeConnection connection = new(["Source1", "Source2"], ["Sink1", "Sink2"]);
|
||||
RunJsonRoundtrip(connection, predicate: connection.CreateValidator());
|
||||
}
|
||||
|
||||
@@ -167,11 +167,9 @@ public class JsonSerializationTests
|
||||
.AddEdge(stringToInt, forwardInt)
|
||||
.AddEdge(forwardInt, intToString);
|
||||
|
||||
Workflow<string, int> workflow = builder.BuildWithOutput<string, int, int>(
|
||||
return builder.BuildWithOutput<string, int, int>(
|
||||
intToString,
|
||||
StreamingAggregators.Last<int>(), (int _, int __) => true);
|
||||
|
||||
return workflow;
|
||||
}
|
||||
|
||||
private static WorkflowInfo TestWorkflowInfo => CreateTestWorkflow().ToWorkflowInfo();
|
||||
@@ -181,10 +179,10 @@ public class JsonSerializationTests
|
||||
ValidateExecutorDictionary(prototype.Executors, prototype.Edges, actual.Executors, actual.Edges);
|
||||
ValidateInputPorts(prototype.InputPorts, actual.InputPorts);
|
||||
|
||||
actual.InputType.Should().Match<TypeId>(prototype.InputType.CreateValidator());
|
||||
actual.InputType.Should().Match(prototype.InputType.CreateValidator());
|
||||
actual.StartExecutorId.Should().Be(prototype.StartExecutorId);
|
||||
|
||||
actual.OutputType.Should().NotBeNull().And.Match<TypeId>(prototype.OutputType!.CreateValidator());
|
||||
actual.OutputType.Should().NotBeNull().And.Match(prototype.OutputType!.CreateValidator());
|
||||
actual.OutputCollectorId.Should().NotBeNull().And.Be(prototype.OutputCollectorId);
|
||||
|
||||
void ValidateExecutorDictionary(Dictionary<string, ExecutorInfo> expected,
|
||||
@@ -202,7 +200,7 @@ public class JsonSerializationTests
|
||||
ExecutorInfo actualValue = actual[key];
|
||||
ExecutorInfo expectedValue = expected[key];
|
||||
|
||||
actualValue.Should().Match<ExecutorInfo>(expectedValue.CreateValidator());
|
||||
actualValue.Should().Match(expectedValue.CreateValidator());
|
||||
|
||||
if (expectedEdges.TryGetValue(key, out List<EdgeInfo>? expectedEdgeList))
|
||||
{
|
||||
@@ -374,9 +372,9 @@ public class JsonSerializationTests
|
||||
[Fact]
|
||||
public void Test_PortableMessageEnvelope_JsonRoundtrip_BuiltInType()
|
||||
{
|
||||
string message = "TestMessage";
|
||||
const string Message = "TestMessage";
|
||||
|
||||
MessageEnvelope envelope = new(message, new TypeId(typeof(object)), targetId: "Target1");
|
||||
MessageEnvelope envelope = new(Message, new TypeId(typeof(object)), targetId: "Target1");
|
||||
PortableMessageEnvelope value = new(envelope);
|
||||
PortableMessageEnvelope result = RunJsonRoundtrip(value);
|
||||
|
||||
@@ -460,9 +458,9 @@ public class JsonSerializationTests
|
||||
outstandingRequests: [TestExternalRequest]
|
||||
);
|
||||
|
||||
Dictionary<ExecutorIdentity, List<PortableMessageEnvelope>> CreateQueuedMessages()
|
||||
static Dictionary<ExecutorIdentity, List<PortableMessageEnvelope>> CreateQueuedMessages()
|
||||
{
|
||||
Dictionary<ExecutorIdentity, List<PortableMessageEnvelope>> result = new();
|
||||
Dictionary<ExecutorIdentity, List<PortableMessageEnvelope>> result = [];
|
||||
|
||||
MessageEnvelope externalEnvelope = new(TestExternalResponse);
|
||||
result.Add(ExecutorIdentity.None, [new(externalEnvelope)]);
|
||||
|
||||
@@ -20,7 +20,7 @@ internal static class MessageDeliveryValidation
|
||||
(string expectedSender, List<string> expectedMessages) = forward;
|
||||
|
||||
return (Action<string>)(
|
||||
(string senderId) =>
|
||||
senderId =>
|
||||
{
|
||||
senderId.Should().Be(expectedSender);
|
||||
queuedMessages[senderId].Should().HaveCount(expectedMessages.Count);
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Agents.Workflows.Execution;
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.Workflows.UnitTests;
|
||||
|
||||
internal static class MessagingTestHelpers
|
||||
{
|
||||
private static void CheckForwarded(Dictionary<string, List<MessageEnvelope>> queuedMessages, params (string expectedSender, List<string> expectedMessages)[] expectedForwards)
|
||||
{
|
||||
queuedMessages.Should().HaveCount(expectedForwards.Length);
|
||||
|
||||
IEnumerable<Action<string>> perSenderValidations = expectedForwards.Select(
|
||||
(forward) =>
|
||||
{
|
||||
(string expectedSender, List<string> expectedMessages) = forward;
|
||||
|
||||
return (Action<string>)(
|
||||
(string senderId) =>
|
||||
{
|
||||
senderId.Should().Be(expectedSender);
|
||||
queuedMessages[senderId].Should().HaveCount(expectedMessages.Count);
|
||||
|
||||
Action<MessageEnvelope>[] validations
|
||||
= expectedMessages.Select(message => (Action<MessageEnvelope>)(envelope => envelope!.Message.Should().Be(message)))
|
||||
.ToArray();
|
||||
|
||||
Assert.Collection(queuedMessages[senderId], validations);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Assert.Collection(queuedMessages.Keys, perSenderValidations.ToArray());
|
||||
}
|
||||
}
|
||||
@@ -10,16 +10,13 @@ namespace Microsoft.Agents.Workflows.UnitTests;
|
||||
|
||||
public class BaseTestExecutor<TActual> : ReflectingExecutor<TActual> where TActual : ReflectingExecutor<TActual>
|
||||
{
|
||||
protected void OnInvokedHandler()
|
||||
{
|
||||
this.InvokedHandler = true;
|
||||
}
|
||||
protected void OnInvokedHandler() => this.InvokedHandler = true;
|
||||
|
||||
public bool InvokedHandler
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
} = false;
|
||||
}
|
||||
}
|
||||
|
||||
public class DefaultHandler : BaseTestExecutor<DefaultHandler>, IMessageHandler<object>
|
||||
@@ -68,7 +65,7 @@ public class TypedHandlerWithOutput<TInput, TResult> : BaseTestExecutor<TypedHan
|
||||
|
||||
public class RoutingReflectionTests
|
||||
{
|
||||
private async ValueTask<CallResult?> RunTestReflectAndRouteMessageAsync<TInput, TE>(BaseTestExecutor<TE> executor, TInput? input = default) where TInput : new() where TE : ReflectingExecutor<TE>
|
||||
private static async ValueTask<CallResult?> RunTestReflectAndRouteMessageAsync<TInput, TE>(BaseTestExecutor<TE> executor, TInput? input = default) where TInput : new() where TE : ReflectingExecutor<TE>
|
||||
{
|
||||
MessageRouter router = executor.Router;
|
||||
|
||||
@@ -89,7 +86,7 @@ public class RoutingReflectionTests
|
||||
{
|
||||
DefaultHandler executor = new();
|
||||
|
||||
CallResult? result = await this.RunTestReflectAndRouteMessageAsync<object, DefaultHandler>(executor);
|
||||
CallResult? result = await RunTestReflectAndRouteMessageAsync<object, DefaultHandler>(executor);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsSuccess);
|
||||
@@ -101,7 +98,7 @@ public class RoutingReflectionTests
|
||||
{
|
||||
TypedHandler<int> executor = new();
|
||||
|
||||
CallResult? result = await this.RunTestReflectAndRouteMessageAsync<object, TypedHandler<int>>(executor, 3);
|
||||
CallResult? result = await RunTestReflectAndRouteMessageAsync<object, TypedHandler<int>>(executor, 3);
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsSuccess);
|
||||
@@ -113,14 +110,11 @@ public class RoutingReflectionTests
|
||||
{
|
||||
TypedHandlerWithOutput<int, string> executor = new()
|
||||
{
|
||||
Handler = (message, context) =>
|
||||
{
|
||||
return new ValueTask<string>($"{message}");
|
||||
}
|
||||
Handler = (message, context) => new ValueTask<string>($"{message}")
|
||||
};
|
||||
|
||||
const string Expected = "3";
|
||||
CallResult? result = await this.RunTestReflectAndRouteMessageAsync<object, TypedHandlerWithOutput<int, string>>(executor, int.Parse(Expected));
|
||||
CallResult? result = await RunTestReflectAndRouteMessageAsync<object, TypedHandlerWithOutput<int, string>>(executor, int.Parse(Expected));
|
||||
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result.IsSuccess);
|
||||
|
||||
@@ -22,31 +22,16 @@ public class RepresentationTests
|
||||
|
||||
private sealed class TestAgent : AIAgent
|
||||
{
|
||||
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private static InputPort TestInputPort =>
|
||||
InputPort.Create<FunctionCallContent, FunctionResultContent>("ExternalFunction");
|
||||
|
||||
private static List<T> ListAggregator<T>(List<T>? current, T incoming)
|
||||
{
|
||||
if (current is null)
|
||||
{
|
||||
return [incoming];
|
||||
}
|
||||
|
||||
current.Add(incoming);
|
||||
return current;
|
||||
}
|
||||
|
||||
private static async ValueTask RunExecutorishInfoMatchTestAsync(ExecutorIsh target)
|
||||
{
|
||||
ExecutorRegistration registration = target.Registration;
|
||||
@@ -59,19 +44,19 @@ public class RepresentationTests
|
||||
public async Task Test_Executorish_InfosAsync()
|
||||
{
|
||||
int testsRun = 0;
|
||||
await RunExecutorishTest(new TestExecutor());
|
||||
await RunExecutorishTest(TestInputPort);
|
||||
await RunExecutorishTest(new TestAgent());
|
||||
await RunExecutorishTestAsync(new TestExecutor());
|
||||
await RunExecutorishTestAsync(TestInputPort);
|
||||
await RunExecutorishTestAsync(new TestAgent());
|
||||
|
||||
Func<int, IWorkflowContext, CancellationToken, ValueTask> function = MessageHandlerAsync;
|
||||
await RunExecutorishTest(function.AsExecutor("FunctionExecutor"));
|
||||
await RunExecutorishTestAsync(function.AsExecutor("FunctionExecutor"));
|
||||
|
||||
if (Enum.GetValues(typeof(ExecutorIsh.Type)).Length > testsRun + 1)
|
||||
{
|
||||
Assert.Fail("Not all ExecutorIsh types were tested.");
|
||||
}
|
||||
|
||||
async ValueTask RunExecutorishTest(ExecutorIsh executorish)
|
||||
async ValueTask RunExecutorishTestAsync(ExecutorIsh executorish)
|
||||
{
|
||||
await RunExecutorishInfoMatchTestAsync(executorish);
|
||||
testsRun++;
|
||||
@@ -92,9 +77,7 @@ public class RepresentationTests
|
||||
await RunExecutorishInfoMatchTestAsync(outputCollector);
|
||||
}
|
||||
|
||||
private static string Source(string id) => $"Source/{id}";
|
||||
private static string Source(int id) => $"Source/{id}";
|
||||
private static string Sink(string id) => $"Sink/{id}";
|
||||
private static string Sink(int id) => $"Sink/{id}";
|
||||
|
||||
private static Func<object?, bool> Condition() => Condition<object>();
|
||||
@@ -156,7 +139,7 @@ public class RepresentationTests
|
||||
RunEdgeInfoMatchTest(fanInEdge, fanInEdge4, expect: false); // Identity matters
|
||||
RunEdgeInfoMatchTest(fanInEdge, fanInEdge5, expect: false);
|
||||
|
||||
void RunEdgeInfoMatchTest(Edge edge, Edge? comparatorEdge = null, bool expect = true)
|
||||
static void RunEdgeInfoMatchTest(Edge edge, Edge? comparatorEdge = null, bool expect = true)
|
||||
{
|
||||
comparatorEdge ??= edge;
|
||||
|
||||
@@ -180,7 +163,7 @@ public class RepresentationTests
|
||||
|
||||
RunWorkflowInfoMatchTest(Step1EntryPoint.WorkflowInstance, Step2EntryPoint.WorkflowInstance, expect: false);
|
||||
|
||||
void RunWorkflowInfoMatchTest<TInput>(Workflow<TInput> workflow, Workflow<TInput>? comparator = null, bool expect = true)
|
||||
static void RunWorkflowInfoMatchTest<TInput>(Workflow<TInput> workflow, Workflow<TInput>? comparator = null, bool expect = true)
|
||||
{
|
||||
comparator ??= workflow;
|
||||
|
||||
|
||||
+4
-8
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.Workflows.Reflection;
|
||||
|
||||
@@ -38,20 +39,15 @@ internal static class Step1EntryPoint
|
||||
|
||||
internal sealed class UppercaseExecutor() : ReflectingExecutor<UppercaseExecutor>("UppercaseExecutor"), IMessageHandler<string, string>
|
||||
{
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context)
|
||||
{
|
||||
string result = message.ToUpperInvariant();
|
||||
return result;
|
||||
}
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context) =>
|
||||
message.ToUpperInvariant();
|
||||
}
|
||||
|
||||
internal sealed class ReverseTextExecutor() : ReflectingExecutor<ReverseTextExecutor>("ReverseTextExecutor"), IMessageHandler<string, string>
|
||||
{
|
||||
public async ValueTask<string> HandleAsync(string message, IWorkflowContext context)
|
||||
{
|
||||
char[] charArray = message.ToCharArray();
|
||||
System.Array.Reverse(charArray);
|
||||
string result = new(charArray);
|
||||
string result = string.Concat(message.Reverse());
|
||||
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(result)).ConfigureAwait(false);
|
||||
return result;
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@ internal static class Step2EntryPoint
|
||||
{
|
||||
get
|
||||
{
|
||||
string[] spamKeywords = { "spam", "advertisement", "offer" };
|
||||
string[] spamKeywords = ["spam", "advertisement", "offer"];
|
||||
|
||||
DetectSpamExecutor detectSpam = new(spamKeywords);
|
||||
RespondToMessageExecutor respondToMessage = new();
|
||||
@@ -84,7 +84,7 @@ internal sealed class RespondToMessageExecutor : ReflectingExecutor<RespondToMes
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Simulate some processing delay
|
||||
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(RespondToMessageExecutor.ActionResult))
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(ActionResult))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ internal sealed class RemoveSpamExecutor : ReflectingExecutor<RemoveSpamExecutor
|
||||
|
||||
await Task.Delay(1000).ConfigureAwait(false); // Simulate some processing delay
|
||||
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(RemoveSpamExecutor.ActionResult))
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent(ActionResult))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
+2
-27
@@ -20,7 +20,7 @@ internal static class Step5EntryPoint
|
||||
await InProcessExecution.StreamAsync(workflow, NumberSignal.Init, checkpointManager)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
List<CheckpointInfo> checkpoints = new();
|
||||
List<CheckpointInfo> checkpoints = [];
|
||||
CancellationTokenSource cancellationSource = new();
|
||||
|
||||
StreamingRun<string> handle = checkpointed.Run;
|
||||
@@ -66,7 +66,7 @@ internal static class Step5EntryPoint
|
||||
{
|
||||
case SuperStepCompletedEvent stepCompletedEvt:
|
||||
CheckpointInfo? checkpoint = stepCompletedEvt.CompletionInfo!.Checkpoint;
|
||||
if (checkpoint != null)
|
||||
if (checkpoint is not null)
|
||||
{
|
||||
checkpoints.Add(checkpoint);
|
||||
}
|
||||
@@ -113,29 +113,4 @@ internal static class Step5EntryPoint
|
||||
|
||||
return request.CreateResponse(result);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This converts the incoming <see cref="NumberSignal"/> from the judge to a status text that can be displayed
|
||||
/// to the user.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This works correctly timing-wise because both the <see cref="StreamingAggregator{TInput, TOutput}"/> and the
|
||||
/// <see cref="InputPort"/> are one edge from the <see cref="JudgeExecutor"/> (see the workflow definition in the
|
||||
/// <see cref="RunAsync"/> method). That means they will get the <see cref="NumberSignal"/> at the same time (one
|
||||
/// SuperStep after the Judge has generated it.)
|
||||
/// </remarks>
|
||||
/// <param name="signal"></param>
|
||||
/// <param name="runningResult"></param>
|
||||
/// <returns></returns>
|
||||
private static string ComputeStreamingOutput(NumberSignal signal, string? runningResult)
|
||||
{
|
||||
return signal switch
|
||||
{
|
||||
NumberSignal.Matched => "You guessed correctly! You Win!",
|
||||
NumberSignal.Above => "Your guess was too high. Try again.",
|
||||
NumberSignal.Below => "Your guess was too low. Try again.",
|
||||
|
||||
_ => runningResult ?? string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,13 +55,13 @@ internal static class Step6EntryPoint
|
||||
|
||||
private sealed class RoundRobinGroupChatManagerOptions : GroupChatManagerOptions
|
||||
{
|
||||
public int? MaxTurns { get; set; } = null;
|
||||
public int? MaxTurns { get; set; }
|
||||
}
|
||||
|
||||
private sealed class RoundRobinGroupChatManager() : GroupChatManager<RoundRobinGroupChatManagerOptions>
|
||||
{
|
||||
public int TurnCount { get; private set; } = 0;
|
||||
public int? MaxTurns { get; private set; } = null;
|
||||
public int TurnCount { get; private set; }
|
||||
public int? MaxTurns { get; private set; }
|
||||
|
||||
protected internal override void Configure(RoundRobinGroupChatManagerOptions options)
|
||||
{
|
||||
@@ -107,14 +107,12 @@ internal sealed class HelloAgent(string id = nameof(HelloAgent)) : AIAgent
|
||||
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
AgentRunResponseUpdate response = new(ChatRole.Assistant, "Hello World!")
|
||||
yield return new(ChatRole.Assistant, "Hello World!")
|
||||
{
|
||||
AgentId = this.Id,
|
||||
AuthorName = this.Name,
|
||||
MessageId = Guid.NewGuid().ToString("N"),
|
||||
};
|
||||
|
||||
yield return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,44 +150,34 @@ internal sealed class EchoAgent(string id = nameof(EchoAgent)) : AIAgent
|
||||
collectedText.AppendLine(messageText);
|
||||
}
|
||||
|
||||
AgentRunResponseUpdate result = new(ChatRole.Assistant, collectedText.ToString())
|
||||
yield return new(ChatRole.Assistant, collectedText.ToString())
|
||||
{
|
||||
AgentId = this.Id,
|
||||
AuthorName = this.Name,
|
||||
MessageId = Guid.NewGuid().ToString("N"),
|
||||
};
|
||||
|
||||
yield return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class GroupChatHistory
|
||||
{
|
||||
private readonly List<ChatMessage> _messages = new();
|
||||
private int _bookmark = 0;
|
||||
private readonly List<ChatMessage> _messages = [];
|
||||
private int _bookmark;
|
||||
|
||||
public void AddMessage(ChatMessage message)
|
||||
{
|
||||
public void AddMessage(ChatMessage message) =>
|
||||
this._messages.Add(message);
|
||||
}
|
||||
|
||||
public void AddMessages(IEnumerable<ChatMessage> messages)
|
||||
{
|
||||
public void AddMessages(IEnumerable<ChatMessage> messages) =>
|
||||
this._messages.AddRange(messages);
|
||||
}
|
||||
|
||||
public void UpdateBookmark()
|
||||
{
|
||||
public void UpdateBookmark() =>
|
||||
this._bookmark = this._messages.Count;
|
||||
}
|
||||
|
||||
public IReadOnlyList<ChatMessage> FullHistory => this._messages.AsReadOnly();
|
||||
public IEnumerable<ChatMessage> NewMessagesThisTurn => this._messages.Skip(this._bookmark);
|
||||
}
|
||||
|
||||
internal class GroupChatManagerOptions
|
||||
{
|
||||
}
|
||||
internal class GroupChatManagerOptions;
|
||||
|
||||
internal abstract class GroupChatManager
|
||||
{
|
||||
@@ -205,8 +193,8 @@ internal abstract class GroupChatManager<TOptions> : GroupChatManager where TOpt
|
||||
|
||||
internal sealed class GroupChatBuilder
|
||||
{
|
||||
private readonly List<ExecutorIsh> _participants = new();
|
||||
private readonly List<bool> _shouldEmitEvents = new();
|
||||
private readonly List<ExecutorIsh> _participants = [];
|
||||
private readonly List<bool> _shouldEmitEvents = [];
|
||||
private readonly Func<string[], GroupChatManager> _managerFactory;
|
||||
|
||||
private GroupChatBuilder(Func<string[], GroupChatManager> managerFactory)
|
||||
@@ -214,10 +202,8 @@ internal sealed class GroupChatBuilder
|
||||
this._managerFactory = managerFactory;
|
||||
}
|
||||
|
||||
public static GroupChatBuilder Create<TManager>() where TManager : GroupChatManager, new()
|
||||
{
|
||||
return new GroupChatBuilder(participantIds => new TManager() { ParticipantIds = participantIds });
|
||||
}
|
||||
public static GroupChatBuilder Create<TManager>() where TManager : GroupChatManager, new() =>
|
||||
new(participantIds => new TManager() { ParticipantIds = participantIds });
|
||||
|
||||
public static GroupChatBuilder Create<TManager, TOptions>(Action<TOptions> configure)
|
||||
where TManager : GroupChatManager<TOptions>, new()
|
||||
@@ -283,12 +269,10 @@ internal sealed class GroupChatBuilder
|
||||
this._autoStartConversation = autoStartConversation;
|
||||
}
|
||||
|
||||
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder)
|
||||
{
|
||||
return routeBuilder.AddHandler<List<ChatMessage>>(this.HandleChatMessagesAsync)
|
||||
.AddHandler<ChatMessage>(this.HandleChatMessageAsync)
|
||||
.AddHandler<TurnToken>(this.AssignNextTurnAsync);
|
||||
}
|
||||
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
||||
routeBuilder.AddHandler<List<ChatMessage>>(this.HandleChatMessagesAsync)
|
||||
.AddHandler<ChatMessage>(this.HandleChatMessageAsync)
|
||||
.AddHandler<TurnToken>(this.AssignNextTurnAsync);
|
||||
|
||||
private async Task TryAutoStartConversationAsync(IWorkflowContext context)
|
||||
{
|
||||
@@ -315,28 +299,26 @@ internal sealed class GroupChatBuilder
|
||||
await this.TryAutoStartConversationAsync(context).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private int _inConversationFlag = 0;
|
||||
private int _inConversationFlag;
|
||||
|
||||
/// <summary>
|
||||
/// Atomically switches to "in conversation" state if not already in that state.
|
||||
/// </summary>
|
||||
/// <returns><see langword="true"/> if the state was changed, <see langword="false"/> otherwise.</returns>
|
||||
private bool TryEnterConversation()
|
||||
{
|
||||
return Interlocked.CompareExchange(ref this._inConversationFlag, 1, 0) == 0;
|
||||
}
|
||||
private bool TryEnterConversation() =>
|
||||
Interlocked.CompareExchange(ref this._inConversationFlag, 1, 0) == 0;
|
||||
|
||||
private bool _shouldHostEmitEvents = false;
|
||||
private bool _shouldHostEmitEvents;
|
||||
private async ValueTask AssignNextTurnAsync(TurnToken token, IWorkflowContext context)
|
||||
{
|
||||
if (this.TryEnterConversation())
|
||||
{
|
||||
// Capture the initial turn token's EmitEvents setting
|
||||
this._shouldHostEmitEvents = token.EmitEvents.HasValue ? token.EmitEvents.Value : false;
|
||||
this._shouldHostEmitEvents = token.EmitEvents ?? false;
|
||||
}
|
||||
|
||||
int? nextSpeakerIndex = this._manager.GetNextTurnExecutor(this._history);
|
||||
if (nextSpeakerIndex == null)
|
||||
if (nextSpeakerIndex is null)
|
||||
{
|
||||
await context.AddEventAsync(new WorkflowCompletedEvent())
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -182,7 +182,7 @@ public class SampleSmokeTest
|
||||
internal sealed class VerifyingPlaybackResponder<TInput, TResponse>
|
||||
{
|
||||
public (TInput input, TResponse response)[] Responses { get; }
|
||||
private int _position = 0;
|
||||
private int _position;
|
||||
|
||||
public VerifyingPlaybackResponder(params (TInput input, TResponse response)[] responses)
|
||||
{
|
||||
|
||||
+18
-32
@@ -24,7 +24,7 @@ public class SpecializedExecutorSmokeTests
|
||||
{
|
||||
List<ChatMessage> result = messages.Select(ToMessage).ToList();
|
||||
|
||||
ChatMessage ToMessage(string text)
|
||||
static ChatMessage ToMessage(string text)
|
||||
{
|
||||
if (string.IsNullOrEmpty(text))
|
||||
{
|
||||
@@ -34,7 +34,7 @@ public class SpecializedExecutorSmokeTests
|
||||
string[] splits = text.Split(' ');
|
||||
for (int i = 0; i < splits.Length - 1; i++)
|
||||
{
|
||||
splits[i] = splits[i] + ' ';
|
||||
splits[i] += ' ';
|
||||
}
|
||||
|
||||
List<AIContent> contents = splits.Select<string, AIContent>(text => new TextContent(text) { RawRepresentation = text }).ToList();
|
||||
@@ -49,21 +49,17 @@ public class SpecializedExecutorSmokeTests
|
||||
return result;
|
||||
}
|
||||
|
||||
public static TestAIAgent FromStrings(params string[] messages)
|
||||
{
|
||||
return new TestAIAgent(ToChatMessages(messages));
|
||||
}
|
||||
public static TestAIAgent FromStrings(params string[] messages) =>
|
||||
new(ToChatMessages(messages));
|
||||
|
||||
public List<ChatMessage> Messages { get; } = Validate(messages) ?? [];
|
||||
|
||||
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return Task.FromResult(new AgentRunResponse(this.Messages)
|
||||
public override Task<AgentRunResponse> RunAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) =>
|
||||
Task.FromResult(new AgentRunResponse(this.Messages)
|
||||
{
|
||||
AgentId = this.Id,
|
||||
ResponseId = Guid.NewGuid().ToString("N")
|
||||
});
|
||||
}
|
||||
|
||||
public override async IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(IEnumerable<ChatMessage> messages, AgentThread? thread = null, AgentRunOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
@@ -88,11 +84,11 @@ public class SpecializedExecutorSmokeTests
|
||||
{
|
||||
string? currentMessageId = null;
|
||||
|
||||
if (candidateMessages != null)
|
||||
if (candidateMessages is not null)
|
||||
{
|
||||
foreach (ChatMessage message in candidateMessages)
|
||||
{
|
||||
if (currentMessageId == null)
|
||||
if (currentMessageId is null)
|
||||
{
|
||||
currentMessageId = message.MessageId;
|
||||
}
|
||||
@@ -109,32 +105,22 @@ public class SpecializedExecutorSmokeTests
|
||||
|
||||
internal sealed class TestWorkflowContext : IWorkflowContext
|
||||
{
|
||||
public List<List<ChatMessage>> Updates { get; } = new();
|
||||
public List<List<ChatMessage>> Updates { get; } = [];
|
||||
|
||||
public ValueTask AddEventAsync(WorkflowEvent workflowEvent)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
public ValueTask AddEventAsync(WorkflowEvent workflowEvent) =>
|
||||
default;
|
||||
|
||||
public ValueTask QueueClearScopeAsync(string? scopeName = null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
public ValueTask QueueClearScopeAsync(string? scopeName = null) =>
|
||||
default;
|
||||
|
||||
public ValueTask QueueStateUpdateAsync<T>(string key, T? value, string? scopeName = null)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
public ValueTask QueueStateUpdateAsync<T>(string key, T? value, string? scopeName = null) =>
|
||||
default;
|
||||
|
||||
public ValueTask<T?> ReadStateAsync<T>(string key, string? scopeName = null)
|
||||
{
|
||||
public ValueTask<T?> ReadStateAsync<T>(string key, string? scopeName = null) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTask<HashSet<string>> ReadStateKeysAsync(string? scopeName = null)
|
||||
{
|
||||
public ValueTask<HashSet<string>> ReadStateKeysAsync(string? scopeName = null) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public ValueTask SendMessageAsync(object message, string? targetId = null)
|
||||
{
|
||||
@@ -166,7 +152,7 @@ public class SpecializedExecutorSmokeTests
|
||||
{
|
||||
for (int i = 0; i < messageSplits.Length - 1; i++)
|
||||
{
|
||||
messageSplits[i] = messageSplits[i] + ' ';
|
||||
messageSplits[i] += ' ';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ public class StateKeyObjectTests
|
||||
ValidateMatch(sharedScope2Key, privateScope1, expectedStrict: false, expectedLoose: false);
|
||||
ValidateMatch(sharedScope2Key, privateScope2, expectedStrict: false, expectedLoose: false);
|
||||
|
||||
void ValidateMatch(UpdateKey key, ScopeId scope, bool expectedStrict, bool expectedLoose)
|
||||
static void ValidateMatch(UpdateKey key, ScopeId scope, bool expectedStrict, bool expectedLoose)
|
||||
{
|
||||
key.IsMatchingScope(scope, strict: true).Should().Be(expectedStrict);
|
||||
key.IsMatchingScope(scope, strict: false).Should().Be(expectedLoose);
|
||||
|
||||
@@ -17,7 +17,7 @@ internal sealed class TestJsonSerializable
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (obj == null)
|
||||
if (obj is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ public class TestRunContext : IRunnerContext
|
||||
=> runnerContext.SendMessageAsync(executorId, message, targetId);
|
||||
}
|
||||
|
||||
public List<WorkflowEvent> Events { get; } = new();
|
||||
public List<WorkflowEvent> Events { get; } = [];
|
||||
|
||||
public ValueTask AddEventAsync(WorkflowEvent workflowEvent)
|
||||
{
|
||||
@@ -38,39 +38,32 @@ public class TestRunContext : IRunnerContext
|
||||
return default;
|
||||
}
|
||||
|
||||
public IWorkflowContext Bind(string executorId)
|
||||
{
|
||||
return new BoundContext(executorId, this);
|
||||
}
|
||||
public IWorkflowContext Bind(string executorId) => new BoundContext(executorId, this);
|
||||
|
||||
public List<ExternalRequest> ExternalRequests { get; } = new();
|
||||
public List<ExternalRequest> ExternalRequests { get; } = [];
|
||||
public ValueTask PostAsync(ExternalRequest request)
|
||||
{
|
||||
this.ExternalRequests.Add(request);
|
||||
return default;
|
||||
}
|
||||
|
||||
internal Dictionary<string, List<MessageEnvelope>> QueuedMessages { get; } = new();
|
||||
internal Dictionary<string, List<MessageEnvelope>> QueuedMessages { get; } = [];
|
||||
public ValueTask SendMessageAsync(string sourceId, object message, string? targetId = null)
|
||||
{
|
||||
if (!this.QueuedMessages.TryGetValue(sourceId, out List<MessageEnvelope>? deliveryQueue))
|
||||
{
|
||||
this.QueuedMessages[sourceId] = deliveryQueue = new();
|
||||
this.QueuedMessages[sourceId] = deliveryQueue = [];
|
||||
}
|
||||
|
||||
deliveryQueue.Add(new(message, targetId: targetId));
|
||||
return default;
|
||||
}
|
||||
|
||||
StepContext IRunnerContext.Advance()
|
||||
{
|
||||
StepContext IRunnerContext.Advance() =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Dictionary<string, Executor> Executors { get; } = new();
|
||||
public Dictionary<string, Executor> Executors { get; } = [];
|
||||
|
||||
ValueTask<Executor> IRunnerContext.EnsureExecutorAsync(string executorId, IStepTracer? tracer)
|
||||
{
|
||||
return new(this.Executors[executorId]);
|
||||
}
|
||||
ValueTask<Executor> IRunnerContext.EnsureExecutorAsync(string executorId, IStepTracer? tracer) =>
|
||||
new(this.Executors[executorId]);
|
||||
}
|
||||
|
||||
@@ -8,23 +8,21 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.Agents.Workflows.UnitTests;
|
||||
|
||||
internal class TestingExecutor<TIn, TOut> : Executor, IDisposable
|
||||
internal abstract class TestingExecutor<TIn, TOut> : Executor, IDisposable
|
||||
{
|
||||
private readonly bool _loop;
|
||||
private readonly Func<TIn, IWorkflowContext, CancellationToken, ValueTask<TOut>>[] _actions;
|
||||
private readonly HashSet<CancellationToken> _linkedTokens = new();
|
||||
private readonly HashSet<CancellationToken> _linkedTokens = [];
|
||||
private CancellationTokenSource _internalCts = new();
|
||||
|
||||
public TestingExecutor(string? id = null, bool loop = false, params Func<TIn, IWorkflowContext, CancellationToken, ValueTask<TOut>>[] actions) : base(id)
|
||||
protected TestingExecutor(string? id = null, bool loop = false, params Func<TIn, IWorkflowContext, CancellationToken, ValueTask<TOut>>[] actions) : base(id)
|
||||
{
|
||||
this._loop = loop;
|
||||
this._actions = actions;
|
||||
}
|
||||
|
||||
public void UnlinkCancellation(CancellationToken token)
|
||||
{
|
||||
public void UnlinkCancellation(CancellationToken token) =>
|
||||
this._linkedTokens.Remove(token);
|
||||
}
|
||||
|
||||
public void LinkCancellation(CancellationToken token)
|
||||
{
|
||||
@@ -34,18 +32,14 @@ internal class TestingExecutor<TIn, TOut> : Executor, IDisposable
|
||||
tokenSource.Dispose();
|
||||
}
|
||||
|
||||
public void SetCancel()
|
||||
{
|
||||
public void SetCancel() =>
|
||||
Volatile.Read(ref this._internalCts).Cancel();
|
||||
}
|
||||
|
||||
protected sealed override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder)
|
||||
{
|
||||
return routeBuilder.AddHandler<TIn, TOut>(this.RouteToActions);
|
||||
}
|
||||
protected sealed override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
||||
routeBuilder.AddHandler<TIn, TOut>(this.RouteToActionsAsync);
|
||||
|
||||
private int _nextActionIndex = 0;
|
||||
private ValueTask<TOut> RouteToActions(TIn message, IWorkflowContext context)
|
||||
private int _nextActionIndex;
|
||||
private ValueTask<TOut> RouteToActionsAsync(TIn message, IWorkflowContext context)
|
||||
{
|
||||
if (this._nextActionIndex >= this._actions.Length)
|
||||
{
|
||||
@@ -75,10 +69,8 @@ internal class TestingExecutor<TIn, TOut> : Executor, IDisposable
|
||||
this.Dispose(false);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
protected virtual void Dispose(bool disposing) =>
|
||||
this._internalCts.Dispose();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
|
||||
@@ -116,12 +116,9 @@ internal static partial class ValidationExtensions
|
||||
innerValidatorExpr
|
||||
);
|
||||
|
||||
Expression<Func<EdgeInfo, bool>> validatorExpr = Expression.Lambda<Func<EdgeInfo, bool>>(
|
||||
return Expression.Lambda<Func<EdgeInfo, bool>>(
|
||||
bodyExpression,
|
||||
outerParam
|
||||
);
|
||||
|
||||
return validatorExpr;
|
||||
outerParam);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,26 +9,16 @@ 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);
|
||||
});
|
||||
}
|
||||
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
||||
routeBuilder.AddHandler<object>(
|
||||
(msg, ctx) => 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);
|
||||
});
|
||||
}
|
||||
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
|
||||
routeBuilder.AddHandler<object>(
|
||||
(msg, ctx) => ctx.SendMessageAsync(msg));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -42,7 +32,7 @@ public partial class WorkflowBuilderSmokeTests
|
||||
|
||||
workflow.Registrations.Should().HaveCount(1);
|
||||
workflow.Registrations.Should().ContainKey("start");
|
||||
workflow.Registrations["start"].ExecutorType.Should().Be(typeof(NoOpExecutor));
|
||||
workflow.Registrations["start"].ExecutorType.Should().Be<NoOpExecutor>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -57,7 +47,7 @@ public partial class WorkflowBuilderSmokeTests
|
||||
|
||||
workflow.Registrations.Should().HaveCount(1);
|
||||
workflow.Registrations.Should().ContainKey("start");
|
||||
workflow.Registrations["start"].ExecutorType.Should().Be(typeof(NoOpExecutor));
|
||||
workflow.Registrations["start"].ExecutorType.Should().Be<NoOpExecutor>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -89,6 +79,6 @@ public partial class WorkflowBuilderSmokeTests
|
||||
|
||||
workflow.Registrations.Should().HaveCount(1);
|
||||
workflow.Registrations.Should().ContainKey("start");
|
||||
workflow.Registrations["start"].ExecutorType.Should().Be(typeof(NoOpExecutor));
|
||||
workflow.Registrations["start"].ExecutorType.Should().Be<NoOpExecutor>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,11 +54,9 @@ public sealed class A2AAgentTests : IDisposable
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithNullA2AClient_ThrowsArgumentNullException()
|
||||
{
|
||||
public void Constructor_WithNullA2AClient_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new A2AAgent(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithDefaultParameters_UsesBaseProperties()
|
||||
@@ -96,10 +94,10 @@ public sealed class A2AAgentTests : IDisposable
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part>
|
||||
{
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Hello! How can I help you today?" }
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var inputMessages = new List<ChatMessage>
|
||||
@@ -139,10 +137,10 @@ public sealed class A2AAgentTests : IDisposable
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part>
|
||||
{
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Response" }
|
||||
},
|
||||
],
|
||||
ContextId = "new-context-id"
|
||||
};
|
||||
|
||||
@@ -194,10 +192,10 @@ public sealed class A2AAgentTests : IDisposable
|
||||
{
|
||||
MessageId = "response-123",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part>
|
||||
{
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Response" }
|
||||
},
|
||||
],
|
||||
ContextId = "different-context"
|
||||
};
|
||||
|
||||
@@ -221,7 +219,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part> { new TextPart { Text = "Hello" } },
|
||||
Parts = [new TextPart { Text = "Hello" }],
|
||||
ContextId = "stream-context"
|
||||
};
|
||||
|
||||
@@ -267,14 +265,14 @@ public sealed class A2AAgentTests : IDisposable
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part> { new TextPart { Text = "Response" } },
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
ContextId = "new-stream-context"
|
||||
};
|
||||
|
||||
var thread = this._agent.GetNewThread();
|
||||
|
||||
// Act
|
||||
await foreach (var update in this._agent.RunStreamingAsync(inputMessages, thread))
|
||||
await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, thread))
|
||||
{
|
||||
// Just iterate through to trigger the logic
|
||||
}
|
||||
@@ -298,7 +296,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
thread.ConversationId = "existing-context-id";
|
||||
|
||||
// Act
|
||||
await foreach (var update in this._agent.RunStreamingAsync(inputMessages, thread))
|
||||
await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, thread))
|
||||
{
|
||||
// Just iterate through to trigger the logic
|
||||
}
|
||||
@@ -325,7 +323,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
{
|
||||
MessageId = "stream-1",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part> { new TextPart { Text = "Response" } },
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
ContextId = "different-context"
|
||||
};
|
||||
|
||||
@@ -362,11 +360,11 @@ public sealed class A2AAgentTests : IDisposable
|
||||
// Arrange
|
||||
var inputMessages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, new List<AIContent>
|
||||
{
|
||||
new(ChatRole.User,
|
||||
[
|
||||
new TextContent("Check this file:"),
|
||||
new HostedFileContent("https://example.com/file.pdf")
|
||||
})
|
||||
])
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -409,7 +407,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
// Return the pre-configured non-streaming response
|
||||
if (this.ResponseToReturn is not null)
|
||||
{
|
||||
var jsonRpcResponse = JsonRpcResponse.CreateJsonRpcResponse<A2AEvent>("response-id", this.ResponseToReturn);
|
||||
var jsonRpcResponse = JsonRpcResponse.CreateJsonRpcResponse("response-id", this.ResponseToReturn);
|
||||
|
||||
return new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
@@ -424,7 +422,7 @@ public sealed class A2AAgentTests : IDisposable
|
||||
await SseFormatter.WriteAsync(
|
||||
new SseItem<JsonRpcResponse>[]
|
||||
{
|
||||
new(JsonRpcResponse.CreateJsonRpcResponse<A2AEvent>("response-id", this.StreamingResponseToReturn!))
|
||||
new(JsonRpcResponse.CreateJsonRpcResponse("response-id", this.StreamingResponseToReturn!))
|
||||
}.ToAsyncEnumerable(),
|
||||
stream,
|
||||
(item, writer) =>
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public sealed class A2ACardResolverExtensionsTests : IDisposable
|
||||
this._handler.ResponsesToReturn.Enqueue(new Message
|
||||
{
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part> { new TextPart { Text = "Response" } },
|
||||
Parts = [new TextPart { Text = "Response" }],
|
||||
});
|
||||
|
||||
var agent = await this._resolver.GetAIAgentAsync(this._httpClient);
|
||||
|
||||
+6
-6
@@ -15,7 +15,7 @@ public sealed class A2AMessageExtensionsTests
|
||||
public void ToChatMessage_WithMixedParts_ReturnsChatMessageWithMixedContents()
|
||||
{
|
||||
// Arrange
|
||||
var uri = "https://example.com/image.jpg";
|
||||
const string Uri = "https://example.com/image.jpg";
|
||||
|
||||
var metadata = new Dictionary<string, JsonElement>
|
||||
{
|
||||
@@ -26,12 +26,12 @@ public sealed class A2AMessageExtensionsTests
|
||||
{
|
||||
MessageId = "mixed-parts-id",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part>
|
||||
{
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Here's an image:" },
|
||||
new FilePart { File = new FileWithUri { Uri = uri } },
|
||||
new FilePart { File = new FileWithUri { Uri = Uri } },
|
||||
new TextPart { Text = "What do you think?" }
|
||||
},
|
||||
],
|
||||
Metadata = metadata
|
||||
};
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed class A2AMessageExtensionsTests
|
||||
Assert.Equal("Here's an image:", firstContent.Text);
|
||||
|
||||
var fileContent = Assert.IsType<HostedFileContent>(result.Contents[1]);
|
||||
Assert.Equal(uri, fileContent.FileId);
|
||||
Assert.Equal(Uri, fileContent.FileId);
|
||||
|
||||
var lastContent = Assert.IsType<TextContent>(result.Contents[2]);
|
||||
Assert.Equal("What do you think?", lastContent.Text);
|
||||
|
||||
+5
-7
@@ -63,8 +63,8 @@ public sealed class A2APartExtensionsTests
|
||||
public void ToAIContent_WithFilePartWithFileWithUri_ReturnsHostedFileContent()
|
||||
{
|
||||
// Arrange
|
||||
var uri = "https://example.com/file.txt";
|
||||
var filePart = new FilePart { File = new FileWithUri { Uri = uri } };
|
||||
const string Uri = "https://example.com/file.txt";
|
||||
var filePart = new FilePart { File = new FileWithUri { Uri = Uri } };
|
||||
|
||||
// Act
|
||||
var result = filePart.ToAIContent();
|
||||
@@ -74,7 +74,7 @@ public sealed class A2APartExtensionsTests
|
||||
Assert.Equal(filePart, result.RawRepresentation);
|
||||
|
||||
var hostedFileContent = Assert.IsType<HostedFileContent>(result);
|
||||
Assert.Equal(uri, hostedFileContent.FileId);
|
||||
Assert.Equal(Uri, hostedFileContent.FileId);
|
||||
Assert.Null(hostedFileContent.AdditionalProperties);
|
||||
}
|
||||
|
||||
@@ -85,12 +85,10 @@ public sealed class A2APartExtensionsTests
|
||||
var customPart = new MockPart();
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<NotSupportedException>(() => customPart.ToAIContent());
|
||||
var exception = Assert.Throws<NotSupportedException>(customPart.ToAIContent);
|
||||
Assert.Equal("Part type 'MockPart' is not supported.", exception.Message);
|
||||
}
|
||||
|
||||
// Mock class for testing unsupported scenarios
|
||||
private sealed class MockPart : Part
|
||||
{
|
||||
}
|
||||
private sealed class MockPart : Part;
|
||||
}
|
||||
|
||||
+5
-7
@@ -31,8 +31,8 @@ public sealed class AIContentExtensionsTests
|
||||
public void ToA2APart_WithHostedFileContent_ReturnsFilePart()
|
||||
{
|
||||
// Arrange
|
||||
var uri = "https://example.com/file.txt";
|
||||
var hostedFileContent = new HostedFileContent(uri);
|
||||
const string Uri = "https://example.com/file.txt";
|
||||
var hostedFileContent = new HostedFileContent(Uri);
|
||||
|
||||
// Act
|
||||
var result = hostedFileContent.ToA2APart();
|
||||
@@ -44,7 +44,7 @@ public sealed class AIContentExtensionsTests
|
||||
Assert.NotNull(filePart.File);
|
||||
|
||||
var fileWithUri = Assert.IsType<FileWithUri>(filePart.File);
|
||||
Assert.Equal(uri, fileWithUri.Uri);
|
||||
Assert.Equal(Uri, fileWithUri.Uri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -54,7 +54,7 @@ public sealed class AIContentExtensionsTests
|
||||
var unsupportedContent = new MockAIContent();
|
||||
|
||||
// Act & Assert
|
||||
var exception = Assert.Throws<NotSupportedException>(() => unsupportedContent.ToA2APart());
|
||||
var exception = Assert.Throws<NotSupportedException>(unsupportedContent.ToA2APart);
|
||||
Assert.Equal("Unsupported content type: MockAIContent.", exception.Message);
|
||||
}
|
||||
|
||||
@@ -106,7 +106,5 @@ public sealed class AIContentExtensionsTests
|
||||
}
|
||||
|
||||
// Mock class for testing unsupported scenarios
|
||||
private sealed class MockAIContent : AIContent
|
||||
{
|
||||
}
|
||||
private sealed class MockAIContent : AIContent;
|
||||
}
|
||||
|
||||
@@ -82,18 +82,18 @@ public class AIAgentTests
|
||||
public async Task InvokeWithStringMessageCallsMockedInvokeWithMessageInCollectionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var message = "Hello, Agent!";
|
||||
const string Message = "Hello, Agent!";
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = default(CancellationToken);
|
||||
|
||||
// Act
|
||||
var response = await this._agentMock.Object.RunAsync(message, this._agentThreadMock.Object, options, cancellationToken);
|
||||
var response = await this._agentMock.Object.RunAsync(Message, this._agentThreadMock.Object, options, cancellationToken);
|
||||
Assert.Equal(this._invokeResponse, response);
|
||||
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First().Text == message),
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First().Text == Message),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
@@ -162,12 +162,12 @@ public class AIAgentTests
|
||||
public async Task InvokeStreamingWithStringMessageCallsMockedInvokeWithMessageInCollectionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var message = "Hello, Agent!";
|
||||
const string Message = "Hello, Agent!";
|
||||
var options = new AgentRunOptions();
|
||||
var cancellationToken = default(CancellationToken);
|
||||
|
||||
// Act
|
||||
await foreach (var response in this._agentMock.Object.RunStreamingAsync(message, this._agentThreadMock.Object, options, cancellationToken))
|
||||
await foreach (var response in this._agentMock.Object.RunStreamingAsync(Message, this._agentThreadMock.Object, options, cancellationToken))
|
||||
{
|
||||
// Assert
|
||||
Assert.Contains(response, this._invokeStreamingResponses);
|
||||
@@ -176,7 +176,7 @@ public class AIAgentTests
|
||||
// Verify that the mocked method was called with the expected parameters
|
||||
this._agentMock.Verify(
|
||||
x => x.RunStreamingAsync(
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First().Text == message),
|
||||
It.Is<IReadOnlyCollection<ChatMessage>>(messages => messages.Count == 1 && messages.First().Text == Message),
|
||||
this._agentThreadMock.Object,
|
||||
options,
|
||||
cancellationToken),
|
||||
@@ -361,28 +361,21 @@ public class AIAgentTests
|
||||
|
||||
private sealed class MockAgent : AIAgent
|
||||
{
|
||||
public static new Task NotifyThreadOfNewMessagesAsync(AgentThread thread, IEnumerable<ChatMessage> messages, CancellationToken cancellationToken)
|
||||
{
|
||||
return AIAgent.NotifyThreadOfNewMessagesAsync(thread, messages, cancellationToken);
|
||||
}
|
||||
public static new Task NotifyThreadOfNewMessagesAsync(AgentThread thread, IEnumerable<ChatMessage> messages, CancellationToken cancellationToken) => AIAgent.NotifyThreadOfNewMessagesAsync(thread, messages, cancellationToken);
|
||||
|
||||
public override Task<AgentRunResponse> RunAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public override IAsyncEnumerable<AgentRunResponseUpdate> RunStreamingAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
AgentThread? thread = null,
|
||||
AgentRunOptions? options = null,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
CancellationToken cancellationToken = default) =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
private static async IAsyncEnumerable<T> ToAsyncEnumerableAsync<T>(IEnumerable<T> values)
|
||||
|
||||
+1
-2
@@ -1,6 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
@@ -14,7 +13,7 @@ public class AIContextProviderTests
|
||||
public async Task InvokedAsync_ReturnsCompletedTaskAsync()
|
||||
{
|
||||
var provider = new TestAIContextProvider();
|
||||
var messages = new ReadOnlyCollection<ChatMessage>(new List<ChatMessage>());
|
||||
var messages = new ReadOnlyCollection<ChatMessage>([]);
|
||||
var task = provider.InvokedAsync(new(messages));
|
||||
Assert.Equal(default, task);
|
||||
}
|
||||
|
||||
+6
-8
@@ -1,7 +1,5 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.Extensions.AI.Agents.Abstractions.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
@@ -25,11 +23,11 @@ public class AIContextTests
|
||||
{
|
||||
var context = new AIContext
|
||||
{
|
||||
Messages = new List<ChatMessage>
|
||||
{
|
||||
Messages =
|
||||
[
|
||||
new(ChatRole.User, "Hello"),
|
||||
new(ChatRole.Assistant, "Hi there!")
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Assert.NotNull(context.Messages);
|
||||
@@ -43,11 +41,11 @@ public class AIContextTests
|
||||
{
|
||||
var context = new AIContext
|
||||
{
|
||||
Tools = new List<AITool>
|
||||
{
|
||||
Tools =
|
||||
[
|
||||
AIFunctionFactory.Create(() => "Function1", "Function1", "Description1"),
|
||||
AIFunctionFactory.Create(() => "Function2", "Function2", "Description2"),
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
Assert.NotNull(context.Tools);
|
||||
|
||||
+1
-3
@@ -21,9 +21,7 @@ public class AgentRunOptionsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloningConstructorThrowsIfNull()
|
||||
{
|
||||
public void CloningConstructorThrowsIfNull() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AgentRunOptions(null!));
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -259,7 +259,7 @@ public class AgentRunResponseTests
|
||||
var response = new AgentRunResponse(new ChatMessage(ChatRole.Assistant, JsonSerializer.Serialize(expectedResult, TestJsonSerializerContext.Default.Animal)));
|
||||
|
||||
// Act.
|
||||
response.TryDeserialize<Animal>(TestJsonSerializerContext.Default.Options, out Animal? animal);
|
||||
response.TryDeserialize(TestJsonSerializerContext.Default.Options, out Animal? animal);
|
||||
|
||||
// Assert.
|
||||
Assert.NotNull(animal);
|
||||
|
||||
+2
-4
@@ -31,10 +31,8 @@ public class AgentRunResponseUpdateExtensionsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ToAgentRunResponseWithInvalidArgsThrows()
|
||||
{
|
||||
public void ToAgentRunResponseWithInvalidArgsThrows() =>
|
||||
Assert.Throws<ArgumentNullException>("updates", () => ((List<AgentRunResponseUpdate>)null!).ToAgentRunResponse());
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(false)]
|
||||
@@ -129,7 +127,7 @@ public class AgentRunResponseUpdateExtensionsTests
|
||||
ChatMessage message = response.Messages.Single();
|
||||
Assert.NotNull(message);
|
||||
|
||||
Assert.Equal(expected.Count + (gapLength * ((numSequences - 1) + (gapBeginningEnd ? 2 : 0))), message.Contents.Count);
|
||||
Assert.Equal(expected.Count + (gapLength * (numSequences - 1 + (gapBeginningEnd ? 2 : 0))), message.Contents.Count);
|
||||
|
||||
TextContent[] contents = message.Contents.OfType<TextContent>().ToArray();
|
||||
Assert.Equal(expected.Count, contents.Length);
|
||||
|
||||
+9
-18
@@ -32,13 +32,13 @@ public class AgentThreadTests
|
||||
{
|
||||
// Arrange
|
||||
var thread = new AgentThread();
|
||||
var conversationid = "test-thread-id";
|
||||
const string Conversationid = "test-thread-id";
|
||||
|
||||
// Act
|
||||
thread.ConversationId = conversationid;
|
||||
thread.ConversationId = Conversationid;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(conversationid, thread.ConversationId);
|
||||
Assert.Equal(Conversationid, thread.ConversationId);
|
||||
Assert.Null(thread.MessageStore);
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public class AgentThreadTests
|
||||
Assert.True(json.TryGetProperty("conversationId", out var idProperty));
|
||||
Assert.Equal("TestConvId", idProperty.GetString());
|
||||
|
||||
Assert.False(json.TryGetProperty("storeState", out var storeStateProperty));
|
||||
Assert.False(json.TryGetProperty("storeState", out _));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -236,8 +236,10 @@ public class AgentThreadTests
|
||||
public async Task VerifyThreadSerializationWithMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
var store = new InMemoryChatMessageStore();
|
||||
store.Add(new ChatMessage(ChatRole.User, "TestContent") { AuthorName = "TestAuthor" });
|
||||
var store = new InMemoryChatMessageStore
|
||||
{
|
||||
new ChatMessage(ChatRole.User, "TestContent") { AuthorName = "TestAuthor" }
|
||||
};
|
||||
var thread = new AgentThread { MessageStore = store };
|
||||
|
||||
// Act
|
||||
@@ -246,7 +248,7 @@ public class AgentThreadTests
|
||||
// Assert
|
||||
Assert.Equal(JsonValueKind.Object, json.ValueKind);
|
||||
|
||||
Assert.False(json.TryGetProperty("conversationId", out var idProperty));
|
||||
Assert.False(json.TryGetProperty("conversationId", out _));
|
||||
|
||||
Assert.True(json.TryGetProperty("storeState", out var storeStateProperty));
|
||||
Assert.Equal(JsonValueKind.Object, storeStateProperty.ValueKind);
|
||||
@@ -329,15 +331,4 @@ public class AgentThreadTests
|
||||
}
|
||||
|
||||
#endregion Serialize Tests
|
||||
|
||||
private static async Task<List<T>> ToListAsync<T>(IAsyncEnumerable<T> values)
|
||||
{
|
||||
var result = new List<T>();
|
||||
await foreach (var v in values)
|
||||
{
|
||||
result.Add(v);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-6
@@ -60,11 +60,9 @@ public class DelegatingAIAgentTests
|
||||
/// Verify that constructor throws ArgumentNullException when innerAgent is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void RequiresInnerAgent()
|
||||
{
|
||||
public void RequiresInnerAgent() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>("innerAgent", () => new TestDelegatingAIAgent(null!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that constructor sets the inner agent correctly.
|
||||
@@ -218,11 +216,9 @@ public class DelegatingAIAgentTests
|
||||
/// Verify that GetService throws ArgumentNullException when serviceType is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void GetServiceThrowsForNullType()
|
||||
{
|
||||
public void GetServiceThrowsForNullType() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>("serviceType", () => this._delegatingAgent.GetService(null!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verify that GetService returns the delegating agent itself when requesting compatible type and key is null.
|
||||
|
||||
+11
-9
@@ -16,11 +16,9 @@ namespace Microsoft.Extensions.AI.Agents.Abstractions.UnitTests;
|
||||
public class InMemoryChatMessageStoreTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_Throws_ForNullReducer()
|
||||
{
|
||||
public void Constructor_Throws_ForNullReducer() =>
|
||||
// Arrange & Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new InMemoryChatMessageStore(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_DefaultsToBeforeMessageRetrieval_ForNotProvidedTriggerEvent()
|
||||
@@ -91,7 +89,7 @@ public class InMemoryChatMessageStoreTests
|
||||
[Fact]
|
||||
public async Task DeserializeConstructorWithEmptyElementAsync()
|
||||
{
|
||||
var emptyObject = JsonSerializer.Deserialize<JsonElement>("{}", TestJsonSerializerContext.Default.JsonElement);
|
||||
var emptyObject = JsonSerializer.Deserialize("{}", TestJsonSerializerContext.Default.JsonElement);
|
||||
|
||||
var newStore = new InMemoryChatMessageStore(emptyObject);
|
||||
|
||||
@@ -304,9 +302,11 @@ public class InMemoryChatMessageStoreTests
|
||||
public void Clear_RemovesAllMessages()
|
||||
{
|
||||
// Arrange
|
||||
var store = new InMemoryChatMessageStore();
|
||||
store.Add(new ChatMessage(ChatRole.User, "First"));
|
||||
store.Add(new ChatMessage(ChatRole.Assistant, "Second"));
|
||||
var store = new InMemoryChatMessageStore
|
||||
{
|
||||
new ChatMessage(ChatRole.User, "First"),
|
||||
new ChatMessage(ChatRole.Assistant, "Second")
|
||||
};
|
||||
|
||||
// Act
|
||||
store.Clear();
|
||||
@@ -527,8 +527,10 @@ public class InMemoryChatMessageStoreTests
|
||||
|
||||
var reducerMock = new Mock<IChatReducer>();
|
||||
|
||||
var store = new InMemoryChatMessageStore(reducerMock.Object, InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded);
|
||||
store.Add(originalMessages[0]);
|
||||
var store = new InMemoryChatMessageStore(reducerMock.Object, InMemoryChatMessageStore.ChatReducerTriggerEvent.AfterMessageAdded)
|
||||
{
|
||||
originalMessages[0]
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = (await store.GetMessagesAsync(CancellationToken.None)).ToList();
|
||||
|
||||
+20
-20
@@ -64,10 +64,10 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test-id",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part>
|
||||
{
|
||||
Parts =
|
||||
[
|
||||
new TextPart { Text = "Hello, world!" }
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -116,13 +116,13 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "user-msg",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { new TextPart { Text = "User message" } }
|
||||
Parts = [new TextPart { Text = "User message" }]
|
||||
},
|
||||
new()
|
||||
{
|
||||
MessageId = "agent-msg",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part> { new TextPart { Text = "Agent response" } }
|
||||
Parts = [new TextPart { Text = "Agent response" }]
|
||||
}
|
||||
};
|
||||
|
||||
@@ -151,7 +151,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "valid-msg",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { new TextPart { Text = "Valid message" } }
|
||||
Parts = [new TextPart { Text = "Valid message" }]
|
||||
},
|
||||
new()
|
||||
{
|
||||
@@ -232,7 +232,7 @@ public class MessageConverterTests
|
||||
var unsupportedContent = new DataContent(new byte[] { 1, 2, 3 }, "image/png");
|
||||
var chatMessage = new ChatMessage(ChatRole.User, [unsupportedContent]);
|
||||
|
||||
var exception = Assert.Throws<NotSupportedException>(() => chatMessage.ToA2AMessage());
|
||||
var exception = Assert.Throws<NotSupportedException>(chatMessage.ToA2AMessage);
|
||||
Assert.Contains("Content type 'DataContent' is not supported", exception.Message);
|
||||
}
|
||||
|
||||
@@ -257,7 +257,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { new TextPart { Text = "Test" } }
|
||||
Parts = [new TextPart { Text = "Test" }]
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
@@ -273,7 +273,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part> { new TextPart { Text = "Test" } }
|
||||
Parts = [new TextPart { Text = "Test" }]
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
@@ -289,7 +289,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test",
|
||||
Role = (MessageRole)999, // Unknown role
|
||||
Parts = new List<Part> { new TextPart { Text = "Test" } }
|
||||
Parts = [new TextPart { Text = "Test" }]
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
@@ -346,7 +346,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { textPart }
|
||||
Parts = [textPart]
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
@@ -374,7 +374,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { textPart }
|
||||
Parts = [textPart]
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
@@ -395,7 +395,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { filePart }
|
||||
Parts = [filePart]
|
||||
};
|
||||
|
||||
var exception = Assert.Throws<NotSupportedException>(() => new List<Message> { message }.ToChatMessages());
|
||||
@@ -410,7 +410,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { dataPart }
|
||||
Parts = [dataPart]
|
||||
};
|
||||
|
||||
var exception = Assert.Throws<NotSupportedException>(() => new List<Message> { message }.ToChatMessages());
|
||||
@@ -429,7 +429,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test-id",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { new TextPart { Text = "Test" } },
|
||||
Parts = [new TextPart { Text = "Test" }],
|
||||
Metadata = metadata
|
||||
};
|
||||
|
||||
@@ -449,7 +449,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test-id",
|
||||
Role = MessageRole.Agent,
|
||||
Parts = new List<Part> { new TextPart { Text = "Test response" } }
|
||||
Parts = [new TextPart { Text = "Test response" }]
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
@@ -465,7 +465,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test-id",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part>() // Empty list
|
||||
Parts = [] // Empty list
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
@@ -502,7 +502,7 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test-id",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { new TextPart { Text = "Test" } },
|
||||
Parts = [new TextPart { Text = "Test" }],
|
||||
Metadata = null
|
||||
};
|
||||
|
||||
@@ -519,8 +519,8 @@ public class MessageConverterTests
|
||||
{
|
||||
MessageId = "test-id",
|
||||
Role = MessageRole.User,
|
||||
Parts = new List<Part> { new TextPart { Text = "Test" } },
|
||||
Metadata = new Dictionary<string, JsonElement>()
|
||||
Parts = [new TextPart { Text = "Test" }],
|
||||
Metadata = []
|
||||
};
|
||||
|
||||
var result = new List<Message> { message }.ToChatMessages();
|
||||
|
||||
@@ -75,12 +75,10 @@ public class AgentProxyTests
|
||||
}
|
||||
private const string AgentName = "agentName";
|
||||
private const string ThreadId = "thread1";
|
||||
private static readonly IReadOnlyCollection<ChatMessage> s_emptyMessages = new List<ChatMessage>();
|
||||
private static readonly IReadOnlyCollection<ChatMessage> s_emptyMessages = [];
|
||||
|
||||
private static bool IsValidGuid(string value)
|
||||
{
|
||||
return Guid.TryParse(value, out _);
|
||||
}
|
||||
private static bool IsValidGuid(string value) =>
|
||||
Guid.TryParse(value, out _);
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that RunAsync returns a deserialized AgentRunResponse when the actor response status is Completed.
|
||||
@@ -228,7 +226,7 @@ public class AgentProxyTests
|
||||
/// Verifies that passing an AgentThread that is not an AgentProxyThread to RunStreamingAsync throws an ArgumentException.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async System.Threading.Tasks.Task RunStreamingAsync_InvalidThread_ThrowsArgumentExceptionAsync()
|
||||
public async Task RunStreamingAsync_InvalidThread_ThrowsArgumentExceptionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var mockClient = new Mock<IActorClient>();
|
||||
@@ -249,7 +247,7 @@ public class AgentProxyTests
|
||||
/// TODO: Mock IActorClient.SendRequestAsync to return an ActorResponseHandle whose WatchUpdatesAsync yields no updates.
|
||||
/// </summary>
|
||||
[Fact(Skip = "Mocking of ActorResponseHandle.WatchUpdatesAsync with IActorClient is required")]
|
||||
public async System.Threading.Tasks.Task RunStreamingAsync_ValidProxyThread_CompletesSuccessfullyAsync()
|
||||
public async Task RunStreamingAsync_ValidProxyThread_CompletesSuccessfullyAsync()
|
||||
{
|
||||
// Arrange
|
||||
var mockClient = new Mock<IActorClient>();
|
||||
@@ -271,7 +269,7 @@ public class AgentProxyTests
|
||||
{
|
||||
// Arrange
|
||||
var messages = Array.Empty<ChatMessage>();
|
||||
var threadId = "thread1";
|
||||
const string ThreadId = "thread1";
|
||||
var expectedUpdate = new AgentRunResponseUpdate(ChatRole.Assistant, "response");
|
||||
|
||||
var updateTypeInfo = AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponseUpdate));
|
||||
@@ -288,7 +286,7 @@ public class AgentProxyTests
|
||||
.ReturnsAsync(mockHandle.Object);
|
||||
|
||||
var proxy = new AgentProxy("agentName", mockClient.Object);
|
||||
var thread = proxy.GetThread(threadId);
|
||||
var thread = proxy.GetThread(ThreadId);
|
||||
|
||||
// Act
|
||||
var results = new List<AgentRunResponseUpdate>();
|
||||
@@ -311,7 +309,7 @@ public class AgentProxyTests
|
||||
{
|
||||
// Arrange
|
||||
var messages = Array.Empty<ChatMessage>();
|
||||
var threadId = "thread1";
|
||||
const string ThreadId = "thread1";
|
||||
|
||||
var agentRunResponse = new AgentRunResponse
|
||||
{
|
||||
@@ -331,7 +329,7 @@ public class AgentProxyTests
|
||||
.ReturnsAsync(mockHandle.Object);
|
||||
|
||||
var proxy = new AgentProxy("agentName", mockClient.Object);
|
||||
var thread = proxy.GetThread(threadId);
|
||||
var thread = proxy.GetThread(ThreadId);
|
||||
|
||||
// Act
|
||||
var results = new List<AgentRunResponseUpdate>();
|
||||
@@ -353,12 +351,12 @@ public class AgentProxyTests
|
||||
{
|
||||
// Arrange: Create a scenario with streaming updates followed by completion
|
||||
var messages = Array.Empty<ChatMessage>();
|
||||
var threadId = "thread1";
|
||||
const string ThreadId = "thread1";
|
||||
|
||||
var pendingUpdate = new AgentRunResponseUpdate(ChatRole.Assistant, "streaming response");
|
||||
var completedResponse = new AgentRunResponse
|
||||
{
|
||||
Messages = new List<ChatMessage> { new(ChatRole.Assistant, "streaming response") }
|
||||
Messages = [new(ChatRole.Assistant, "streaming response")]
|
||||
};
|
||||
|
||||
var updates = new List<ActorRequestUpdate>
|
||||
@@ -379,7 +377,7 @@ public class AgentProxyTests
|
||||
.ReturnsAsync(mockHandle.Object);
|
||||
|
||||
var proxy = new AgentProxy("agentName", mockClient.Object);
|
||||
var thread = proxy.GetThread(threadId);
|
||||
var thread = proxy.GetThread(ThreadId);
|
||||
|
||||
// Act
|
||||
var results = new List<AgentRunResponseUpdate>();
|
||||
@@ -418,7 +416,7 @@ public class AgentProxyTests
|
||||
{
|
||||
// Arrange
|
||||
var messages = Array.Empty<ChatMessage>();
|
||||
var threadId = "thread1";
|
||||
const string ThreadId = "thread1";
|
||||
var expectedUpdate = new AgentRunResponseUpdate(ChatRole.Assistant, "response");
|
||||
var updateTypeInfo = AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponseUpdate));
|
||||
var jsonElement = JsonSerializer.SerializeToElement(expectedUpdate, updateTypeInfo);
|
||||
@@ -434,7 +432,7 @@ public class AgentProxyTests
|
||||
.ReturnsAsync(mockHandle.Object);
|
||||
|
||||
var proxy = new AgentProxy("agentName", mockClient.Object);
|
||||
var thread = proxy.GetThread(threadId);
|
||||
var thread = proxy.GetThread(ThreadId);
|
||||
|
||||
// Act & Assert
|
||||
var exception = await Assert.ThrowsAsync<InvalidOperationException>(async () =>
|
||||
@@ -451,11 +449,9 @@ public class AgentProxyTests
|
||||
/// Verifies that constructor throws ArgumentNullException when client is null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Constructor_NullClient_ThrowsArgumentNullException()
|
||||
{
|
||||
public void Constructor_NullClient_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AgentProxy("agentName", null!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that constructor throws ArgumentNullException when name is null.
|
||||
@@ -675,14 +671,14 @@ public class AgentProxyTests
|
||||
// Arrange
|
||||
var mockClient = new Mock<IActorClient>();
|
||||
var mockHandle = new Mock<ActorResponseHandle>();
|
||||
var expectedMessageId = "custom-message-id";
|
||||
const string ExpectedMessageId = "custom-message-id";
|
||||
var response = new AgentRunResponse { Messages = [] };
|
||||
var jsonElement = JsonSerializer.SerializeToElement(response,
|
||||
AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(AgentRunResponse)));
|
||||
var actorResponse = new ActorResponse
|
||||
{
|
||||
ActorId = new ActorId(AgentName, ThreadId),
|
||||
MessageId = expectedMessageId,
|
||||
MessageId = ExpectedMessageId,
|
||||
Data = jsonElement,
|
||||
Status = RequestStatus.Completed
|
||||
};
|
||||
@@ -698,7 +694,7 @@ public class AgentProxyTests
|
||||
var messages = new List<ChatMessage>
|
||||
{
|
||||
new(ChatRole.User, "first"),
|
||||
new(ChatRole.User, "last") { MessageId = expectedMessageId }
|
||||
new(ChatRole.User, "last") { MessageId = ExpectedMessageId }
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -706,7 +702,7 @@ public class AgentProxyTests
|
||||
|
||||
// Assert
|
||||
mockClient.Verify(c => c.SendRequestAsync(
|
||||
It.Is<ActorRequest>(r => r.MessageId == expectedMessageId),
|
||||
It.Is<ActorRequest>(r => r.MessageId == ExpectedMessageId),
|
||||
It.IsAny<CancellationToken>()), Times.Once);
|
||||
}
|
||||
|
||||
@@ -797,12 +793,12 @@ public class AgentProxyTests
|
||||
public override bool TryGetResponse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] out ActorResponse? response)
|
||||
{
|
||||
response = this._response;
|
||||
return this._response != null;
|
||||
return this._response is not null;
|
||||
}
|
||||
|
||||
public override ValueTask<ActorResponse> GetResponseAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this._response == null)
|
||||
if (this._response is null)
|
||||
{
|
||||
throw new InvalidOperationException("No response configured");
|
||||
}
|
||||
|
||||
+29
-36
@@ -12,36 +12,36 @@ public class AgentProxyThreadTests
|
||||
/// <summary>
|
||||
/// Provides valid identifier values that conform to RFC 3986 unreserved characters.
|
||||
/// </summary>
|
||||
public static IEnumerable<object[]> ValidIds => new List<object[]>
|
||||
{
|
||||
new object[] { "normal" },
|
||||
new object[] { "test-id" },
|
||||
new object[] { "test_id" },
|
||||
new object[] { "test.id" },
|
||||
new object[] { "test~id" },
|
||||
new object[] { "ABC123" },
|
||||
new object[] { "a" },
|
||||
new object[] { "123" },
|
||||
new object[] { "test-id_with.various~chars" },
|
||||
new object[] { new string('a', 100) } // Long but valid ID
|
||||
};
|
||||
public static IEnumerable<object[]> ValidIds { get; } =
|
||||
[
|
||||
["normal"],
|
||||
["test-id"],
|
||||
["test_id"],
|
||||
["test.id"],
|
||||
["test~id"],
|
||||
["ABC123"],
|
||||
["a"],
|
||||
["123"],
|
||||
["test-id_with.various~chars"],
|
||||
[new string('a', 100)] // Long but valid ID
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Provides invalid identifier values that violate the RFC 3986 unreserved character rules.
|
||||
/// </summary>
|
||||
public static IEnumerable<object[]> InvalidIds => new List<object[]>
|
||||
{
|
||||
new object[] { " " }, // Space not allowed
|
||||
new object[] { "!@#$%^&*()" }, // Special characters not allowed
|
||||
new object[] { "test id" }, // Space not allowed
|
||||
new object[] { "test/id" }, // Forward slash not allowed
|
||||
new object[] { "test?id" }, // Question mark not allowed
|
||||
new object[] { "test#id" }, // Hash not allowed
|
||||
new object[] { "test@id" }, // At symbol not allowed
|
||||
new object[] { "test id with spaces" }, // Multiple spaces not allowed
|
||||
new object[] { "test\tid" }, // Tab not allowed
|
||||
new object[] { "test\nid" }, // Newline not allowed
|
||||
};
|
||||
public static IEnumerable<object[]> InvalidIds { get; } =
|
||||
[
|
||||
[" "], // Space not allowed
|
||||
["!@#$%^&*()"], // Special characters not allowed
|
||||
["test id"], // Space not allowed
|
||||
["test/id"], // Forward slash not allowed
|
||||
["test?id"], // Question mark not allowed
|
||||
["test#id"], // Hash not allowed
|
||||
["test@id"], // At symbol not allowed
|
||||
["test id with spaces"], // Multiple spaces not allowed
|
||||
["test\tid"], // Tab not allowed
|
||||
["test\nid"], // Newline not allowed
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that providing valid id to <see cref="AgentProxyThread"/> constructor sets the Id property correctly.
|
||||
@@ -76,21 +76,17 @@ public class AgentProxyThreadTests
|
||||
/// Verifies that providing a null id to <see cref="AgentProxyThread"/> constructor throws an <see cref="ArgumentNullException"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Constructor_NullId_ThrowsArgumentNullException()
|
||||
{
|
||||
public void Constructor_NullId_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new AgentProxyThread(null!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that providing an empty id to <see cref="AgentProxyThread"/> constructor throws an <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Constructor_EmptyId_ThrowsArgumentException()
|
||||
{
|
||||
public void Constructor_EmptyId_ThrowsArgumentException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => new AgentProxyThread(""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the default constructor initializes the Id property with a valid non-empty GUID string in "N" format.
|
||||
@@ -160,10 +156,7 @@ public class AgentProxyThreadTests
|
||||
var ids = new string[NumberOfIds];
|
||||
|
||||
// Act - Create IDs in parallel to test thread safety
|
||||
Parallel.For(0, NumberOfIds, i =>
|
||||
{
|
||||
ids[i] = AgentProxyThread.CreateId();
|
||||
});
|
||||
Parallel.For(0, NumberOfIds, i => ids[i] = AgentProxyThread.CreateId());
|
||||
|
||||
// Assert
|
||||
var uniqueIds = ids.Distinct().Count();
|
||||
|
||||
+5
-9
@@ -14,12 +14,10 @@ public class HostApplicationBuilderAgentExtensionsTests
|
||||
/// Verifies that providing a null builder to AddAIAgent throws an ArgumentNullException.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AddAIAgent_NullBuilder_ThrowsArgumentNullException()
|
||||
{
|
||||
public void AddAIAgent_NullBuilder_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(
|
||||
() => HostApplicationBuilderAgentExtensions.AddAIAgent(null!, "agent", "instructions"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AddAIAgent with valid parameters returns the same builder instance.
|
||||
@@ -106,15 +104,13 @@ public class HostApplicationBuilderAgentExtensionsTests
|
||||
/// Verifies that AddAIAgent with factory delegate throws ArgumentNullException for null builder.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void AddAIAgentWithFactory_NullBuilder_ThrowsArgumentNullException()
|
||||
{
|
||||
public void AddAIAgentWithFactory_NullBuilder_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
HostApplicationBuilderAgentExtensions.AddAIAgent(
|
||||
null!,
|
||||
"agentName",
|
||||
(sp, key) => new Mock<AIAgent>().Object));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that AddAIAgent with factory delegate throws ArgumentNullException for null name.
|
||||
@@ -179,7 +175,7 @@ public class HostApplicationBuilderAgentExtensionsTests
|
||||
|
||||
// Assert
|
||||
var descriptor = builder.Services.FirstOrDefault(
|
||||
d => d.ServiceKey as string == AgentName &&
|
||||
d => (d.ServiceKey as string) == AgentName &&
|
||||
d.ServiceType == typeof(AIAgent));
|
||||
|
||||
Assert.NotNull(descriptor);
|
||||
@@ -275,7 +271,7 @@ public class HostApplicationBuilderAgentExtensionsTests
|
||||
Assert.Same(builder, result);
|
||||
// The agent should be registered (proving the method chain worked)
|
||||
var descriptor = builder.Services.FirstOrDefault(
|
||||
d => d.ServiceKey as string == "agentName" &&
|
||||
d => d.ServiceKey is "agentName" &&
|
||||
d.ServiceType == typeof(AIAgent));
|
||||
Assert.NotNull(descriptor);
|
||||
}
|
||||
@@ -302,7 +298,7 @@ public class HostApplicationBuilderAgentExtensionsTests
|
||||
// Assert
|
||||
Assert.Same(builder, result);
|
||||
var descriptor = builder.Services.FirstOrDefault(
|
||||
d => d.ServiceKey as string == name &&
|
||||
d => (d.ServiceKey as string) == name &&
|
||||
d.ServiceType == typeof(AIAgent));
|
||||
Assert.NotNull(descriptor);
|
||||
}
|
||||
|
||||
+65
-77
@@ -10,69 +10,69 @@ public class ActorTypeTests
|
||||
/// <summary>
|
||||
/// Provides valid ActorType names that conform to the regex pattern ^[a-zA-Z_][a-zA-Z._:\-0-9]*$.
|
||||
/// </summary>
|
||||
public static IEnumerable<object[]> ValidActorTypeNames => new List<object[]>
|
||||
{
|
||||
new object[] { "a" }, // Single letter
|
||||
new object[] { "A" }, // Single uppercase letter
|
||||
new object[] { "_" }, // Single underscore
|
||||
new object[] { "agent" }, // Simple name
|
||||
new object[] { "Agent" }, // Capitalized name
|
||||
new object[] { "AGENT" }, // All caps name
|
||||
new object[] { "my_agent" }, // With underscore
|
||||
new object[] { "MyAgent" }, // Camel case
|
||||
new object[] { "agent1" }, // With number
|
||||
new object[] { "agent_1" }, // With underscore and number
|
||||
new object[] { "agent:type" }, // With colon
|
||||
new object[] { "agent-type" }, // With hyphen
|
||||
new object[] { "my_agent:type-1" }, // Complex valid name
|
||||
new object[] { "A1_test:complex-name" }, // Very complex valid name
|
||||
new object[] { "_private_agent" }, // Starting with underscore
|
||||
new object[] { "agent_with_many_underscores" }, // Multiple underscores
|
||||
new object[] { "agent:with:colons" }, // Multiple colons
|
||||
new object[] { "agent-with-hyphens" }, // Multiple hyphens
|
||||
new object[] { "agent123456789" }, // With many numbers
|
||||
new object[] { "agent.type" }, // With dot
|
||||
new object[] { "agent.sub.type" }, // With multiple dots
|
||||
new object[] { "my.agent_1:type-name" }, // Complex with dots
|
||||
};
|
||||
public static IEnumerable<object[]> ValidActorTypeNames { get; } =
|
||||
[
|
||||
["a"], // Single letter
|
||||
["A"], // Single uppercase letter
|
||||
["_"], // Single underscore
|
||||
["agent"], // Simple name
|
||||
["Agent"], // Capitalized name
|
||||
["AGENT"], // All caps name
|
||||
["my_agent"], // With underscore
|
||||
["MyAgent"], // Camel case
|
||||
["agent1"], // With number
|
||||
["agent_1"], // With underscore and number
|
||||
["agent:type"], // With colon
|
||||
["agent-type"], // With hyphen
|
||||
["my_agent:type-1"], // Complex valid name
|
||||
["A1_test:complex-name"], // Very complex valid name
|
||||
["_private_agent"], // Starting with underscore
|
||||
["agent_with_many_underscores"], // Multiple underscores
|
||||
["agent:with:colons"], // Multiple colons
|
||||
["agent-with-hyphens"], // Multiple hyphens
|
||||
["agent123456789"], // With many numbers
|
||||
["agent.type"], // With dot
|
||||
["agent.sub.type"], // With multiple dots
|
||||
["my.agent_1:type-name"], // Complex with dots
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Provides invalid ActorType names that violate the regex pattern ^[a-zA-Z_][a-zA-Z._:\-0-9]*$.
|
||||
/// </summary>
|
||||
public static IEnumerable<object[]> InvalidActorTypeNames => new List<object[]>
|
||||
{
|
||||
new object[] { "1agent" }, // Starting with number
|
||||
new object[] { "9test" }, // Starting with number
|
||||
new object[] { "-agent" }, // Starting with hyphen
|
||||
new object[] { ":agent" }, // Starting with colon
|
||||
new object[] { " agent" }, // Starting with space
|
||||
new object[] { "agent " }, // Trailing space
|
||||
new object[] { "agent agent" }, // Space in middle
|
||||
new object[] { "agent@type" }, // Invalid character @
|
||||
new object[] { "agent#type" }, // Invalid character #
|
||||
new object[] { "agent$type" }, // Invalid character $
|
||||
new object[] { "agent%type" }, // Invalid character %
|
||||
new object[] { "agent^type" }, // Invalid character ^
|
||||
new object[] { "agent&type" }, // Invalid character &
|
||||
new object[] { "agent*type" }, // Invalid character *
|
||||
new object[] { "agent(type)" }, // Invalid characters ( )
|
||||
new object[] { "agent[type]" }, // Invalid characters [ ]
|
||||
new object[] { "agent{type}" }, // Invalid characters { }
|
||||
new object[] { "agent+type" }, // Invalid character +
|
||||
new object[] { "agent=type" }, // Invalid character =
|
||||
new object[] { "agent\\type" }, // Invalid character \
|
||||
new object[] { "agent/type" }, // Invalid character /
|
||||
new object[] { "agent?type" }, // Invalid character ?
|
||||
new object[] { "agent,type" }, // Invalid character ,
|
||||
new object[] { "agent;type" }, // Invalid character ;
|
||||
new object[] { "agent\"type" }, // Invalid character "
|
||||
new object[] { "agent'type" }, // Invalid character '
|
||||
new object[] { "agent`type" }, // Invalid character `
|
||||
new object[] { "agent~type" }, // Invalid character ~
|
||||
new object[] { "agent!type" }, // Invalid character !
|
||||
new object[] { "agent\ttype" }, // Tab character
|
||||
new object[] { "agent\ntype" }, // Newline character
|
||||
};
|
||||
public static IEnumerable<object[]> InvalidActorTypeNames { get; } =
|
||||
[
|
||||
["1agent"], // Starting with number
|
||||
["9test"], // Starting with number
|
||||
["-agent"], // Starting with hyphen
|
||||
[":agent"], // Starting with colon
|
||||
[" agent"], // Starting with space
|
||||
["agent "], // Trailing space
|
||||
["agent agent"], // Space in middle
|
||||
["agent@type"], // Invalid character @
|
||||
["agent#type"], // Invalid character #
|
||||
["agent$type"], // Invalid character $
|
||||
["agent%type"], // Invalid character %
|
||||
["agent^type"], // Invalid character ^
|
||||
["agent&type"], // Invalid character &
|
||||
["agent*type"], // Invalid character *
|
||||
["agent(type)"], // Invalid characters ( )
|
||||
["agent[type]"], // Invalid characters [ ]
|
||||
["agent{type}"], // Invalid characters { }
|
||||
["agent+type"], // Invalid character +
|
||||
["agent=type"], // Invalid character =
|
||||
["agent\\type"], // Invalid character \
|
||||
["agent/type"], // Invalid character /
|
||||
["agent?type"], // Invalid character ?
|
||||
["agent,type"], // Invalid character ,
|
||||
["agent;type"], // Invalid character ;
|
||||
["agent\"type"], // Invalid character "
|
||||
["agent'type"], // Invalid character '
|
||||
["agent`type"], // Invalid character `
|
||||
["agent~type"], // Invalid character ~
|
||||
["agent!type"], // Invalid character !
|
||||
["agent\ttype"], // Tab character
|
||||
["agent\ntype"], // Newline character
|
||||
];
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that providing valid actor type name to <see cref="ActorType"/> constructor sets the Name property correctly.
|
||||
@@ -107,21 +107,17 @@ public class ActorTypeTests
|
||||
/// Verifies that providing a null type name to <see cref="ActorType"/> constructor throws an <see cref="ArgumentNullException"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Constructor_NullTypeName_ThrowsArgumentNullException()
|
||||
{
|
||||
public void Constructor_NullTypeName_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new ActorType(null!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that providing an empty type name to <see cref="ActorType"/> constructor throws an <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Constructor_EmptyTypeName_ThrowsArgumentException()
|
||||
{
|
||||
public void Constructor_EmptyTypeName_ThrowsArgumentException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => new ActorType(""));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies specific edge cases for valid type names.
|
||||
@@ -258,40 +254,32 @@ public class ActorTypeTests
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[MemberData(nameof(ValidActorTypeNames))]
|
||||
public void IsValidType_ValidTypeName_ReturnsTrue(string typeName)
|
||||
{
|
||||
public void IsValidType_ValidTypeName_ReturnsTrue(string typeName) =>
|
||||
// Act & Assert
|
||||
Assert.True(ActorType.IsValidType(typeName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that IsValidType static method works correctly for invalid names.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[MemberData(nameof(InvalidActorTypeNames))]
|
||||
public void IsValidType_InvalidTypeName_ReturnsFalse(string typeName)
|
||||
{
|
||||
public void IsValidType_InvalidTypeName_ReturnsFalse(string typeName) =>
|
||||
// Act & Assert
|
||||
Assert.False(ActorType.IsValidType(typeName));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that IsValidType throws for null.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsValidType_NullTypeName_ThrowsArgumentNullException()
|
||||
{
|
||||
public void IsValidType_NullTypeName_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => ActorType.IsValidType(null!));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that IsValidType throws for empty string.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void IsValidType_EmptyTypeName_ThrowsArgumentException()
|
||||
{
|
||||
public void IsValidType_EmptyTypeName_ThrowsArgumentException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => ActorType.IsValidType(""));
|
||||
}
|
||||
}
|
||||
|
||||
+67
-71
@@ -19,14 +19,14 @@ public sealed class InMemoryActorStateStorageTests
|
||||
private readonly ActorId _anotherActorId = new("AnotherActor", "another-instance");
|
||||
|
||||
[Fact]
|
||||
public async Task WriteStateAsync_WithSetValueOperation_ShouldStoreValue()
|
||||
public async Task WriteStateAsync_WithSetValueOperation_ShouldStoreValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -39,23 +39,23 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteStateAsync_WithRemoveKeyOperation_ShouldRemoveValue()
|
||||
public async Task WriteStateAsync_WithRemoveKeyOperation_ShouldRemoveValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
|
||||
// First set a value
|
||||
var setOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
var setResult = await this._storage.WriteStateAsync(this._testActorId, setOperations, "0", CancellationToken.None);
|
||||
|
||||
// Now remove the value
|
||||
var removeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new RemoveKeyOperation(key)
|
||||
new RemoveKeyOperation(Key)
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -68,14 +68,14 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteStateAsync_WithIncorrectETag_ShouldReturnFailure()
|
||||
public async Task WriteStateAsync_WithIncorrectETag_ShouldReturnFailureAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -88,20 +88,20 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithGetValueOperation_ShouldReturnValue()
|
||||
public async Task ReadStateAsync_WithGetValueOperation_ShouldReturnValueAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
await this._storage.WriteStateAsync(this._testActorId, writeOperations, "0", CancellationToken.None);
|
||||
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -116,7 +116,7 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithGetValueOperationForNonExistentKey_ShouldReturnNull()
|
||||
public async Task ReadStateAsync_WithGetValueOperationForNonExistentKey_ShouldReturnNullAsync()
|
||||
{
|
||||
// Arrange
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
@@ -135,18 +135,18 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithListKeysOperation_ShouldReturnAllKeys()
|
||||
public async Task ReadStateAsync_WithListKeysOperation_ShouldReturnAllKeysAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1),
|
||||
new SetValueOperation(key2, value2)
|
||||
new SetValueOperation(Key1, value1),
|
||||
new SetValueOperation(Key2, value2)
|
||||
};
|
||||
await this._storage.WriteStateAsync(this._testActorId, writeOperations, "0", CancellationToken.None);
|
||||
|
||||
@@ -163,13 +163,13 @@ public sealed class InMemoryActorStateStorageTests
|
||||
var listKeys = result.Results[0] as ListKeysResult;
|
||||
Assert.NotNull(listKeys);
|
||||
Assert.Equal(2, listKeys.Keys.Count);
|
||||
Assert.Contains(key1, listKeys.Keys);
|
||||
Assert.Contains(key2, listKeys.Keys);
|
||||
Assert.Contains(Key1, listKeys.Keys);
|
||||
Assert.Contains(Key2, listKeys.Keys);
|
||||
Assert.Null(listKeys.ContinuationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithListKeysOperationForEmptyActor_ShouldReturnEmptyList()
|
||||
public async Task ReadStateAsync_WithListKeysOperationForEmptyActor_ShouldReturnEmptyListAsync()
|
||||
{
|
||||
// Arrange
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
@@ -189,21 +189,21 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithListKeysOperationAndKeyPrefix_ShouldReturnFilteredKeys()
|
||||
public async Task ReadStateAsync_WithListKeysOperationAndKeyPrefix_ShouldReturnFilteredKeysAsync()
|
||||
{
|
||||
// Arrange
|
||||
var prefixKey1 = "prefix_key1";
|
||||
var prefixKey2 = "prefix_key2";
|
||||
var otherKey = "other_key";
|
||||
const string PrefixKey1 = "prefix_key1";
|
||||
const string PrefixKey2 = "prefix_key2";
|
||||
const string OtherKey = "other_key";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
var value3 = JsonSerializer.SerializeToElement("value3");
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(prefixKey1, value1),
|
||||
new SetValueOperation(prefixKey2, value2),
|
||||
new SetValueOperation(otherKey, value3)
|
||||
new SetValueOperation(PrefixKey1, value1),
|
||||
new SetValueOperation(PrefixKey2, value2),
|
||||
new SetValueOperation(OtherKey, value3)
|
||||
};
|
||||
await this._storage.WriteStateAsync(this._testActorId, writeOperations, "0", CancellationToken.None);
|
||||
|
||||
@@ -220,25 +220,25 @@ public sealed class InMemoryActorStateStorageTests
|
||||
var listKeys = result.Results[0] as ListKeysResult;
|
||||
Assert.NotNull(listKeys);
|
||||
Assert.Equal(2, listKeys.Keys.Count);
|
||||
Assert.Contains(prefixKey1, listKeys.Keys);
|
||||
Assert.Contains(prefixKey2, listKeys.Keys);
|
||||
Assert.DoesNotContain(otherKey, listKeys.Keys);
|
||||
Assert.Contains(PrefixKey1, listKeys.Keys);
|
||||
Assert.Contains(PrefixKey2, listKeys.Keys);
|
||||
Assert.DoesNotContain(OtherKey, listKeys.Keys);
|
||||
Assert.Null(listKeys.ContinuationToken);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithListKeysOperationAndNonMatchingKeyPrefix_ShouldReturnEmptyList()
|
||||
public async Task ReadStateAsync_WithListKeysOperationAndNonMatchingKeyPrefix_ShouldReturnEmptyListAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
var writeOperations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1),
|
||||
new SetValueOperation(key2, value2)
|
||||
new SetValueOperation(Key1, value1),
|
||||
new SetValueOperation(Key2, value2)
|
||||
};
|
||||
await this._storage.WriteStateAsync(this._testActorId, writeOperations, "0", CancellationToken.None);
|
||||
|
||||
@@ -259,19 +259,19 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MultipleOperations_ShouldBeProcessedInOrder()
|
||||
public async Task MultipleOperations_ShouldBeProcessedInOrderAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key1 = "key1";
|
||||
var key2 = "key2";
|
||||
const string Key1 = "key1";
|
||||
const string Key2 = "key2";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key1, value1),
|
||||
new SetValueOperation(key2, value2),
|
||||
new RemoveKeyOperation(key1)
|
||||
new SetValueOperation(Key1, value1),
|
||||
new SetValueOperation(Key2, value2),
|
||||
new RemoveKeyOperation(Key1)
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -284,7 +284,7 @@ public sealed class InMemoryActorStateStorageTests
|
||||
// Verify remaining key
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key2)
|
||||
new GetValueOperation(Key2)
|
||||
};
|
||||
var readResult = await this._storage.ReadStateAsync(this._testActorId, readOperations, CancellationToken.None);
|
||||
var getValue = readResult.Results[0] as GetValueResult;
|
||||
@@ -293,20 +293,20 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DifferentActors_ShouldHaveIsolatedState()
|
||||
public async Task DifferentActors_ShouldHaveIsolatedStateAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key = "sharedKey";
|
||||
const string Key = "sharedKey";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
var operations1 = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value1)
|
||||
new SetValueOperation(Key, value1)
|
||||
};
|
||||
var operations2 = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value2)
|
||||
new SetValueOperation(Key, value2)
|
||||
};
|
||||
|
||||
// Act
|
||||
@@ -321,7 +321,7 @@ public sealed class InMemoryActorStateStorageTests
|
||||
// Verify values are different
|
||||
var readOperations = new List<ActorStateReadOperation>
|
||||
{
|
||||
new GetValueOperation(key)
|
||||
new GetValueOperation(Key)
|
||||
};
|
||||
|
||||
var result1 = await this._storage.ReadStateAsync(this._testActorId, readOperations, CancellationToken.None);
|
||||
@@ -337,14 +337,14 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ConcurrentOperations_ShouldBeThreadSafe()
|
||||
public async Task ConcurrentOperations_ShouldBeThreadSafeAsync()
|
||||
{
|
||||
// Arrange
|
||||
const int operationCount = 100;
|
||||
const int OperationCount = 100;
|
||||
var tasks = new List<Task>();
|
||||
|
||||
// Act
|
||||
for (int i = 0; i < operationCount; i++)
|
||||
for (int i = 0; i < OperationCount; i++)
|
||||
{
|
||||
var key = $"key{i}";
|
||||
var value = JsonSerializer.SerializeToElement($"value{i}");
|
||||
@@ -359,9 +359,9 @@ public sealed class InMemoryActorStateStorageTests
|
||||
// Retry logic to handle concurrent updates
|
||||
var success = false;
|
||||
var retryCount = 0;
|
||||
const int maxRetries = 10;
|
||||
const int MaxRetries = 10;
|
||||
|
||||
while (!success && retryCount < maxRetries)
|
||||
while (!success && retryCount < MaxRetries)
|
||||
{
|
||||
var currentETag = this._storage.GetETag(actorId);
|
||||
var result = await this._storage.WriteStateAsync(actorId, operations, currentETag, CancellationToken.None);
|
||||
@@ -385,14 +385,14 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Clear_ShouldRemoveAllState()
|
||||
public async Task Clear_ShouldRemoveAllStateAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value = JsonSerializer.SerializeToElement("testValue");
|
||||
var operations = new List<ActorStateWriteOperation>
|
||||
{
|
||||
new SetValueOperation(key, value)
|
||||
new SetValueOperation(Key, value)
|
||||
};
|
||||
|
||||
await this._storage.WriteStateAsync(this._testActorId, operations, "0", CancellationToken.None);
|
||||
@@ -418,15 +418,13 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteStateAsync_WithNullOperations_ShouldThrowArgumentNullException()
|
||||
{
|
||||
public async Task WriteStateAsync_WithNullOperations_ShouldThrowArgumentNullExceptionAsync() =>
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
||||
this._storage.WriteStateAsync(this._testActorId, null!, "0", CancellationToken.None).AsTask());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteStateAsync_WithNullETag_ShouldThrowArgumentNullException()
|
||||
public async Task WriteStateAsync_WithNullETag_ShouldThrowArgumentNullExceptionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var operations = new List<ActorStateWriteOperation>();
|
||||
@@ -437,15 +435,13 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithNullOperations_ShouldThrowArgumentNullException()
|
||||
{
|
||||
public async Task ReadStateAsync_WithNullOperations_ShouldThrowArgumentNullExceptionAsync() =>
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
||||
this._storage.ReadStateAsync(this._testActorId, null!, CancellationToken.None).AsTask());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteStateAsync_WithCancelledToken_ShouldThrowOperationCanceledException()
|
||||
public async Task WriteStateAsync_WithCancelledToken_ShouldThrowOperationCanceledExceptionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var operations = new List<ActorStateWriteOperation>();
|
||||
@@ -457,7 +453,7 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ReadStateAsync_WithCancelledToken_ShouldThrowOperationCanceledException()
|
||||
public async Task ReadStateAsync_WithCancelledToken_ShouldThrowOperationCanceledExceptionAsync()
|
||||
{
|
||||
// Arrange
|
||||
var operations = new List<ActorStateReadOperation>();
|
||||
@@ -469,18 +465,18 @@ public sealed class InMemoryActorStateStorageTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ETagProgression_ShouldIncrementMonotonically()
|
||||
public async Task ETagProgression_ShouldIncrementMonotonicallyAsync()
|
||||
{
|
||||
// Arrange
|
||||
var key = "testKey";
|
||||
const string Key = "testKey";
|
||||
var value1 = JsonSerializer.SerializeToElement("value1");
|
||||
var value2 = JsonSerializer.SerializeToElement("value2");
|
||||
|
||||
// Act
|
||||
var operations1 = new List<ActorStateWriteOperation> { new SetValueOperation(key, value1) };
|
||||
var operations1 = new List<ActorStateWriteOperation> { new SetValueOperation(Key, value1) };
|
||||
var result1 = await this._storage.WriteStateAsync(this._testActorId, operations1, "0", CancellationToken.None);
|
||||
|
||||
var operations2 = new List<ActorStateWriteOperation> { new SetValueOperation(key, value2) };
|
||||
var operations2 = new List<ActorStateWriteOperation> { new SetValueOperation(Key, value2) };
|
||||
var result2 = await this._storage.WriteStateAsync(this._testActorId, operations2, result1.ETag, CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
|
||||
@@ -37,7 +37,6 @@ public class AgentExtensionsTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.True(result is AIFunction);
|
||||
Assert.Equal("TestAgent", result.Name);
|
||||
Assert.Equal("Test agent description", result.Description);
|
||||
}
|
||||
@@ -301,7 +300,7 @@ public class AgentExtensionsTests
|
||||
public override string? Name { get; }
|
||||
public override string? Description { get; }
|
||||
|
||||
public List<ChatMessage> ReceivedMessages { get; } = new();
|
||||
public List<ChatMessage> ReceivedMessages { get; } = [];
|
||||
public CancellationToken LastCancellationToken { get; private set; }
|
||||
public int RunAsyncCallCount { get; private set; }
|
||||
|
||||
@@ -315,7 +314,7 @@ public class AgentExtensionsTests
|
||||
this.LastCancellationToken = cancellationToken;
|
||||
this.ReceivedMessages.AddRange(messages);
|
||||
|
||||
if (this._exceptionToThrow != null)
|
||||
if (this._exceptionToThrow is not null)
|
||||
{
|
||||
throw this._exceptionToThrow;
|
||||
}
|
||||
|
||||
+11
-11
@@ -441,7 +441,7 @@ public class ChatClientAgentTests
|
||||
{
|
||||
capturedMessages.AddRange(msgs);
|
||||
capturedInstructions = opts.Instructions ?? string.Empty;
|
||||
if (opts.Tools != null)
|
||||
if (opts.Tools is not null)
|
||||
{
|
||||
capturedTools.AddRange(opts.Tools);
|
||||
}
|
||||
@@ -536,7 +536,7 @@ public class ChatClientAgentTests
|
||||
{
|
||||
capturedMessages.AddRange(msgs);
|
||||
capturedInstructions = opts.Instructions ?? string.Empty;
|
||||
if (opts.Tools != null)
|
||||
if (opts.Tools is not null)
|
||||
{
|
||||
capturedTools.AddRange(opts.Tools);
|
||||
}
|
||||
@@ -804,7 +804,7 @@ public class ChatClientAgentTests
|
||||
{
|
||||
// Arrange
|
||||
var chatClient = new Mock<IChatClient>().Object;
|
||||
ChatClientAgent agent = new(chatClient, options: null!);
|
||||
ChatClientAgent agent = new(chatClient, options: null);
|
||||
|
||||
// Act & Assert
|
||||
Assert.NotNull(agent.Id);
|
||||
@@ -1324,7 +1324,7 @@ public class ChatClientAgentTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsAssignableFrom<IChatClient>(result);
|
||||
Assert.IsType<IChatClient>(result, exactMatch: false);
|
||||
|
||||
// Note: The result will be the AgentInvokedChatClient wrapper, not the original mock
|
||||
Assert.Equal("AgentInvokedChatClient", result.GetType().Name);
|
||||
@@ -1388,8 +1388,8 @@ public class ChatClientAgentTests
|
||||
// Arrange
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var customService = new object();
|
||||
var serviceKey = "test-key";
|
||||
mockChatClient.Setup(c => c.GetService(typeof(string), serviceKey))
|
||||
const string ServiceKey = "test-key";
|
||||
mockChatClient.Setup(c => c.GetService(typeof(string), ServiceKey))
|
||||
.Returns(customService);
|
||||
|
||||
var agent = new ChatClientAgent(mockChatClient.Object, new ChatClientAgentOptions
|
||||
@@ -1398,11 +1398,11 @@ public class ChatClientAgentTests
|
||||
});
|
||||
|
||||
// Act
|
||||
var result = agent.GetService(typeof(string), serviceKey);
|
||||
var result = agent.GetService(typeof(string), ServiceKey);
|
||||
|
||||
// Assert
|
||||
Assert.Same(customService, result);
|
||||
mockChatClient.Verify(c => c.GetService(typeof(string), serviceKey), Times.Once);
|
||||
mockChatClient.Verify(c => c.GetService(typeof(string), ServiceKey), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1417,7 +1417,7 @@ public class ChatClientAgentTests
|
||||
{
|
||||
// Arrange
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var chatClientMetadata = providerName != null ? new ChatClientMetadata(providerName) : null;
|
||||
var chatClientMetadata = providerName is not null ? new ChatClientMetadata(providerName) : null;
|
||||
mockChatClient.Setup(c => c.GetService(typeof(ChatClientMetadata), null))
|
||||
.Returns(chatClientMetadata);
|
||||
|
||||
@@ -1448,7 +1448,7 @@ public class ChatClientAgentTests
|
||||
{
|
||||
// Arrange
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var chatClientMetadata = chatClientProviderName != null ? new ChatClientMetadata(chatClientProviderName) : null;
|
||||
var chatClientMetadata = chatClientProviderName is not null ? new ChatClientMetadata(chatClientProviderName) : null;
|
||||
mockChatClient.Setup(c => c.GetService(typeof(ChatClientMetadata), null))
|
||||
.Returns(chatClientMetadata);
|
||||
|
||||
@@ -1615,7 +1615,7 @@ public class ChatClientAgentTests
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.IsAssignableFrom<IChatClient>(result);
|
||||
Assert.IsType<IChatClient>(result, exactMatch: false);
|
||||
|
||||
// Verify that the ChatClient's GetService was NOT called because IChatClient is handled by the agent itself
|
||||
mockChatClient.Verify(c => c.GetService(typeof(IChatClient), "some-key"), Times.Never);
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Microsoft.Extensions.AI.Agents.UnitTests;
|
||||
/// </summary>
|
||||
public class CopilotStudioAgentTests
|
||||
{
|
||||
private CopilotClient CreateTestCopilotClient()
|
||||
private static CopilotClient CreateTestCopilotClient()
|
||||
{
|
||||
// Create mock dependencies for CopilotClient
|
||||
var mockSettings = new Mock<ConnectionSettings>();
|
||||
@@ -33,7 +33,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_RequestingCopilotClient_ReturnsCopilotClient()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
@@ -51,7 +51,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_RequestingAIAgentMetadata_ReturnsMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
@@ -71,7 +71,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_RequestingUnknownServiceType_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
@@ -88,7 +88,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_WithServiceKey_ReturnsNull()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
@@ -105,7 +105,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_RequestingCopilotStudioAgentType_ReturnsBaseImplementation()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
@@ -123,7 +123,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_RequestingAIAgentType_ReturnsBaseImplementation()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
@@ -141,7 +141,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_RequestingCopilotClientWithServiceKey_CallsBaseFirstThenDerivedLogic()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act - Request CopilotClient with a service key (base.GetService will return null due to serviceKey)
|
||||
@@ -159,7 +159,7 @@ public class CopilotStudioAgentTests
|
||||
public void GetService_RequestingAIAgentMetadata_ReturnsConsistentMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var client = this.CreateTestCopilotClient();
|
||||
var client = CreateTestCopilotClient();
|
||||
var agent = new CopilotStudioAgent(client, NullLoggerFactory.Instance);
|
||||
|
||||
// Act
|
||||
|
||||
@@ -447,14 +447,14 @@ public class OpenTelemetryAgentTests
|
||||
var mockLogger = new Mock<ILogger>();
|
||||
mockLoggerFactory.Setup(f => f.CreateLogger(It.IsAny<string>()))
|
||||
.Returns(mockLogger.Object);
|
||||
var sourceName = "custom-source";
|
||||
var enableSensitiveData = true;
|
||||
const string SourceName = "custom-source";
|
||||
const bool EnableSensitiveData = true;
|
||||
|
||||
// Act
|
||||
using var telemetryAgent = mockAgent.Object.WithOpenTelemetry(
|
||||
loggerFactory: mockLoggerFactory.Object,
|
||||
sourceName: sourceName,
|
||||
enableSensitiveData: enableSensitiveData);
|
||||
sourceName: SourceName,
|
||||
enableSensitiveData: EnableSensitiveData);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<OpenTelemetryAgent>(telemetryAgent);
|
||||
@@ -553,10 +553,10 @@ public class OpenTelemetryAgentTests
|
||||
mockAgent.Setup(a => a.Id).Returns("test-id");
|
||||
mockAgent.Setup(a => a.Name).Returns("TestAgent");
|
||||
var mockLogger = new Mock<ILogger>();
|
||||
var sourceName = "test-source";
|
||||
const string SourceName = "test-source";
|
||||
|
||||
// Act
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, mockLogger.Object, sourceName);
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, mockLogger.Object, SourceName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-id", telemetryAgent.Id);
|
||||
@@ -573,10 +573,10 @@ public class OpenTelemetryAgentTests
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-id");
|
||||
mockAgent.Setup(a => a.Name).Returns("TestAgent");
|
||||
var sourceName = "test-source";
|
||||
const string SourceName = "test-source";
|
||||
|
||||
// Act
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, logger: null, sourceName);
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, logger: null, SourceName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-id", telemetryAgent.Id);
|
||||
@@ -633,10 +633,10 @@ public class OpenTelemetryAgentTests
|
||||
var mockLogger = new Mock<ILogger>();
|
||||
mockLoggerFactory.Setup(f => f.CreateLogger(It.IsAny<string>()))
|
||||
.Returns(mockLogger.Object);
|
||||
var sourceName = "test-source";
|
||||
const string SourceName = "test-source";
|
||||
|
||||
// Act
|
||||
using var telemetryAgent = mockAgent.Object.WithOpenTelemetry(mockLoggerFactory.Object, sourceName);
|
||||
using var telemetryAgent = mockAgent.Object.WithOpenTelemetry(mockLoggerFactory.Object, SourceName);
|
||||
|
||||
// Assert
|
||||
Assert.IsType<OpenTelemetryAgent>(telemetryAgent);
|
||||
@@ -835,8 +835,8 @@ public class OpenTelemetryAgentTests
|
||||
// Arrange
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-id");
|
||||
var sourceName = "test-source";
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, sourceName: sourceName);
|
||||
const string SourceName = "test-source";
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, sourceName: SourceName);
|
||||
|
||||
// Act
|
||||
var result = telemetryAgent.GetService(typeof(ActivitySource));
|
||||
@@ -845,7 +845,7 @@ public class OpenTelemetryAgentTests
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<ActivitySource>(result);
|
||||
var activitySource = (ActivitySource)result;
|
||||
Assert.Equal(sourceName, activitySource.Name);
|
||||
Assert.Equal(SourceName, activitySource.Name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -900,18 +900,18 @@ public class OpenTelemetryAgentTests
|
||||
// Arrange
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
var customService = new object();
|
||||
var serviceKey = "test-key";
|
||||
mockAgent.Setup(a => a.GetService(typeof(string), serviceKey))
|
||||
const string ServiceKey = "test-key";
|
||||
mockAgent.Setup(a => a.GetService(typeof(string), ServiceKey))
|
||||
.Returns(customService);
|
||||
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object);
|
||||
|
||||
// Act
|
||||
var result = telemetryAgent.GetService(typeof(string), serviceKey);
|
||||
var result = telemetryAgent.GetService(typeof(string), ServiceKey);
|
||||
|
||||
// Assert
|
||||
Assert.Same(customService, result);
|
||||
mockAgent.Verify(a => a.GetService(typeof(string), serviceKey), Times.Once);
|
||||
mockAgent.Verify(a => a.GetService(typeof(string), ServiceKey), Times.Once);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -926,8 +926,8 @@ public class OpenTelemetryAgentTests
|
||||
mockAgent.Setup(a => a.GetService(typeof(ActivitySource), null))
|
||||
.Returns(innerActivitySource);
|
||||
|
||||
var sourceName = "telemetry-source";
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, sourceName: sourceName);
|
||||
const string SourceName = "telemetry-source";
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, sourceName: SourceName);
|
||||
|
||||
// Act
|
||||
var result = telemetryAgent.GetService(typeof(ActivitySource));
|
||||
@@ -936,7 +936,7 @@ public class OpenTelemetryAgentTests
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<ActivitySource>(result);
|
||||
var activitySource = (ActivitySource)result;
|
||||
Assert.Equal(sourceName, activitySource.Name);
|
||||
Assert.Equal(SourceName, activitySource.Name);
|
||||
Assert.NotSame(innerActivitySource, result); // Should return OpenTelemetryAgent's ActivitySource, not inner agent's
|
||||
|
||||
// Cleanup
|
||||
@@ -1017,8 +1017,8 @@ public class OpenTelemetryAgentTests
|
||||
// Arrange
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-id");
|
||||
var sourceName = "test-source";
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, sourceName: sourceName);
|
||||
const string SourceName = "test-source";
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, sourceName: SourceName);
|
||||
|
||||
// Act - Request ActivitySource with a service key (base.GetService will return null due to serviceKey)
|
||||
var result = telemetryAgent.GetService(typeof(ActivitySource), "some-key");
|
||||
@@ -1027,7 +1027,7 @@ public class OpenTelemetryAgentTests
|
||||
Assert.NotNull(result);
|
||||
Assert.IsType<ActivitySource>(result);
|
||||
var activitySource = (ActivitySource)result;
|
||||
Assert.Equal(sourceName, activitySource.Name);
|
||||
Assert.Equal(SourceName, activitySource.Name);
|
||||
// Verify that the inner agent's GetService was NOT called because ActivitySource is handled by the telemetry agent itself
|
||||
mockAgent.Verify(a => a.GetService(typeof(ActivitySource), "some-key"), Times.Never);
|
||||
}
|
||||
@@ -1200,9 +1200,9 @@ public class OpenTelemetryAgentTests
|
||||
.AddInMemoryExporter(activities)
|
||||
.Build();
|
||||
|
||||
var customProviderName = "custom-ai-provider";
|
||||
const string CustomProviderName = "custom-ai-provider";
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
var customMetadata = new AIAgentMetadata(customProviderName);
|
||||
var customMetadata = new AIAgentMetadata(CustomProviderName);
|
||||
|
||||
// Setup mock agent to return custom metadata
|
||||
mockAgent.Setup(a => a.GetService(typeof(AIAgentMetadata), null))
|
||||
@@ -1224,12 +1224,12 @@ public class OpenTelemetryAgentTests
|
||||
var activity = Assert.Single(activities);
|
||||
|
||||
// Verify that the custom provider name appears in telemetry
|
||||
Assert.Equal(customProviderName, activity.GetTagItem(OpenTelemetryConsts.GenAI.SystemName));
|
||||
Assert.Equal(CustomProviderName, activity.GetTagItem(OpenTelemetryConsts.GenAI.SystemName));
|
||||
|
||||
// Verify that GetService returns the same custom provider name
|
||||
var agentMetadata = telemetryAgent.GetService(typeof(AIAgentMetadata)) as AIAgentMetadata;
|
||||
Assert.NotNull(agentMetadata);
|
||||
Assert.Equal(customProviderName, agentMetadata.ProviderName);
|
||||
Assert.Equal(CustomProviderName, agentMetadata.ProviderName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1351,9 +1351,9 @@ public class OpenTelemetryAgentTests
|
||||
.AddInMemoryExporter(activities)
|
||||
.Build();
|
||||
|
||||
var providerName = "consistent-provider";
|
||||
const string ProviderName = "consistent-provider";
|
||||
var mockChatClient = new Mock<IChatClient>();
|
||||
var chatClientMetadata = new ChatClientMetadata(providerName);
|
||||
var chatClientMetadata = new ChatClientMetadata(ProviderName);
|
||||
mockChatClient.Setup(c => c.GetService(typeof(ChatClientMetadata), null))
|
||||
.Returns(chatClientMetadata);
|
||||
mockChatClient.Setup(c => c.GetResponseAsync(It.IsAny<IEnumerable<ChatMessage>>(), It.IsAny<ChatOptions>(), It.IsAny<CancellationToken>()))
|
||||
@@ -1382,7 +1382,7 @@ public class OpenTelemetryAgentTests
|
||||
// Verify that all activities have the same provider name
|
||||
foreach (var activity in activities)
|
||||
{
|
||||
Assert.Equal(providerName, activity.GetTagItem(OpenTelemetryConsts.GenAI.SystemName));
|
||||
Assert.Equal(ProviderName, activity.GetTagItem(OpenTelemetryConsts.GenAI.SystemName));
|
||||
}
|
||||
|
||||
// Verify that GetService consistently returns the same provider name
|
||||
@@ -1390,8 +1390,8 @@ public class OpenTelemetryAgentTests
|
||||
var agentMetadata2 = telemetryAgent.GetService(typeof(AIAgentMetadata)) as AIAgentMetadata;
|
||||
Assert.NotNull(agentMetadata1);
|
||||
Assert.NotNull(agentMetadata2);
|
||||
Assert.Equal(providerName, agentMetadata1.ProviderName);
|
||||
Assert.Equal(providerName, agentMetadata2.ProviderName);
|
||||
Assert.Equal(ProviderName, agentMetadata1.ProviderName);
|
||||
Assert.Equal(ProviderName, agentMetadata2.ProviderName);
|
||||
Assert.Same(agentMetadata1, agentMetadata2); // Should be cached
|
||||
}
|
||||
|
||||
@@ -1457,26 +1457,26 @@ public class OpenTelemetryAgentTests
|
||||
if (throwError)
|
||||
{
|
||||
mockAgent.Setup(a => a.RunStreamingAsync(It.IsAny<IReadOnlyCollection<ChatMessage>>(), It.IsAny<AgentThread>(), It.IsAny<AgentRunOptions>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(ThrowingAsyncEnumerable());
|
||||
.Returns(ThrowingAsyncEnumerableAsync());
|
||||
}
|
||||
else
|
||||
{
|
||||
mockAgent.Setup(a => a.RunStreamingAsync(It.IsAny<IReadOnlyCollection<ChatMessage>>(), It.IsAny<AgentThread>(), It.IsAny<AgentRunOptions>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(CreateStreamingResponse());
|
||||
.Returns(CreateStreamingResponseAsync());
|
||||
}
|
||||
|
||||
return mockAgent;
|
||||
|
||||
static async IAsyncEnumerable<AgentRunResponseUpdate> ThrowingAsyncEnumerable([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
static async IAsyncEnumerable<AgentRunResponseUpdate> ThrowingAsyncEnumerableAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Yield();
|
||||
throw new InvalidOperationException("Streaming error");
|
||||
#pragma warning disable CS0162 // Unreachable code detected
|
||||
yield break;
|
||||
#pragma warning restore CS0162 // Unreachable code detected
|
||||
#pragma warning restore CS0162
|
||||
}
|
||||
|
||||
static async IAsyncEnumerable<AgentRunResponseUpdate> CreateStreamingResponse([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
static async IAsyncEnumerable<AgentRunResponseUpdate> CreateStreamingResponseAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
@@ -1503,11 +1503,9 @@ public class OpenTelemetryAgentTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_NullAgent_ThrowsArgumentNullException()
|
||||
{
|
||||
public void Constructor_NullAgent_ThrowsArgumentNullException() =>
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() => new OpenTelemetryAgent(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithParameters_SetsProperties()
|
||||
@@ -1520,10 +1518,10 @@ public class OpenTelemetryAgentTests
|
||||
var mockLogger = new Mock<ILogger>();
|
||||
|
||||
var logger = new Mock<ILogger>().Object;
|
||||
var sourceName = "custom-source";
|
||||
const string SourceName = "custom-source";
|
||||
|
||||
// Act
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, mockLogger.Object, sourceName);
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, mockLogger.Object, SourceName);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("test-id", telemetryAgent.Id);
|
||||
@@ -1652,7 +1650,7 @@ public class OpenTelemetryAgentTests
|
||||
mockAgent.Setup(a => a.Name).Returns("TestAgent");
|
||||
|
||||
mockAgent.Setup(a => a.RunStreamingAsync(It.IsAny<IReadOnlyCollection<ChatMessage>>(), It.IsAny<AgentThread>(), It.IsAny<AgentRunOptions>(), It.IsAny<CancellationToken>()))
|
||||
.Returns(CreatePartialStreamingResponse());
|
||||
.Returns(CreatePartialStreamingResponseAsync());
|
||||
|
||||
using var telemetryAgent = new OpenTelemetryAgent(mockAgent.Object, sourceName: sourceName);
|
||||
|
||||
@@ -1674,7 +1672,7 @@ public class OpenTelemetryAgentTests
|
||||
var activity = Assert.Single(activities);
|
||||
Assert.Equal("partial-response-id", activity.GetTagItem(OpenTelemetryConsts.GenAI.Response.Id));
|
||||
|
||||
static async IAsyncEnumerable<AgentRunResponseUpdate> CreatePartialStreamingResponse([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
static async IAsyncEnumerable<AgentRunResponseUpdate> CreatePartialStreamingResponseAsync([EnumeratorCancellation] CancellationToken cancellationToken = default)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
@@ -2051,10 +2049,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2104,10 +2099,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2157,10 +2149,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2223,10 +2212,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2314,10 +2300,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2382,10 +2365,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2466,10 +2446,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2520,10 +2497,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2587,10 +2561,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
@@ -2698,10 +2669,7 @@ public class OpenTelemetryAgentTests
|
||||
It.IsAny<It.IsAnyType>(),
|
||||
It.IsAny<Exception>(),
|
||||
It.IsAny<Func<It.IsAnyType, Exception?, string>>()))
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) =>
|
||||
{
|
||||
loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? ""));
|
||||
});
|
||||
.Callback<LogLevel, EventId, object, Exception, Delegate>((level, eventId, state, ex, formatter) => loggedEvents.Add((level, eventId, formatter.DynamicInvoke(state, ex)?.ToString() ?? "")));
|
||||
|
||||
var mockAgent = new Mock<AIAgent>();
|
||||
mockAgent.Setup(a => a.Id).Returns("test-agent");
|
||||
|
||||
@@ -66,10 +66,8 @@ public class OpenAIAssistantFixture : IChatClientAgentFixture
|
||||
});
|
||||
}
|
||||
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent)
|
||||
{
|
||||
return this._assistantClient!.DeleteAssistantAsync(agent.Id);
|
||||
}
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent) =>
|
||||
this._assistantClient!.DeleteAssistantAsync(agent.Id);
|
||||
|
||||
public Task DeleteThreadAsync(AgentThread thread)
|
||||
{
|
||||
|
||||
@@ -30,10 +30,8 @@ public class OpenAIChatCompletionFixture : IChatClientAgentFixture
|
||||
|
||||
public IChatClient ChatClient => this._agent.ChatClient;
|
||||
|
||||
public async Task<List<ChatMessage>> GetChatHistoryAsync(AgentThread thread)
|
||||
{
|
||||
return thread.MessageStore is null ? [] : (await thread.MessageStore.GetMessagesAsync()).ToList();
|
||||
}
|
||||
public async Task<List<ChatMessage>> GetChatHistoryAsync(AgentThread thread) =>
|
||||
thread.MessageStore is null ? [] : (await thread.MessageStore.GetMessagesAsync()).ToList();
|
||||
|
||||
public Task<ChatClientAgent> CreateChatClientAgentAsync(
|
||||
string name = "HelpfulAssistant",
|
||||
@@ -52,25 +50,17 @@ public class OpenAIChatCompletionFixture : IChatClientAgentFixture
|
||||
}));
|
||||
}
|
||||
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent)
|
||||
{
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent) =>
|
||||
// Chat Completion does not require/support deleting agents, so this is a no-op.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task DeleteThreadAsync(AgentThread thread)
|
||||
{
|
||||
public Task DeleteThreadAsync(AgentThread thread) =>
|
||||
// Chat Completion does not require/support deleting threads, so this is a no-op.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Task.CompletedTask;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
public async Task InitializeAsync() =>
|
||||
this._agent = await this.CreateChatClientAgentAsync();
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task DisposeAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
+4
-8
@@ -10,10 +10,8 @@ public class OpenAIResponseStoreTrueChatClientAgentRunStreamingTests() : ChatCli
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
public class OpenAIResponseStoreFalseChatClientAgentRunStreamingTests() : ChatClientAgentRunStreamingTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
@@ -21,8 +19,6 @@ public class OpenAIResponseStoreFalseChatClientAgentRunStreamingTests() : ChatCl
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
+4
-8
@@ -10,10 +10,8 @@ public class OpenAIResponseStoreTrueChatClientAgentRunTests() : ChatClientAgentR
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
public class OpenAIResponseStoreFalseChatClientAgentRunTests() : ChatClientAgentRunTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
@@ -21,8 +19,6 @@ public class OpenAIResponseStoreFalseChatClientAgentRunTests() : ChatClientAgent
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -64,36 +64,30 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
|
||||
throw new NotSupportedException("This test currently only supports text messages");
|
||||
}
|
||||
|
||||
public Task<ChatClientAgent> CreateChatClientAgentAsync(
|
||||
public async Task<ChatClientAgent> CreateChatClientAgentAsync(
|
||||
string name = "HelpfulAssistant",
|
||||
string instructions = "You are a helpful assistant.",
|
||||
IList<AITool>? aiTools = null)
|
||||
{
|
||||
return Task.FromResult(new ChatClientAgent(
|
||||
this._openAIResponseClient.AsIChatClient(),
|
||||
options: new()
|
||||
{
|
||||
Name = name,
|
||||
Instructions = instructions,
|
||||
ChatOptions = new ChatOptions
|
||||
IList<AITool>? aiTools = null) =>
|
||||
new ChatClientAgent(
|
||||
this._openAIResponseClient.AsIChatClient(),
|
||||
options: new()
|
||||
{
|
||||
Tools = aiTools,
|
||||
RawRepresentationFactory = new Func<IChatClient, object>((_) => new ResponseCreationOptions() { StoredOutputEnabled = store })
|
||||
},
|
||||
}));
|
||||
}
|
||||
Name = name,
|
||||
Instructions = instructions,
|
||||
ChatOptions = new ChatOptions
|
||||
{
|
||||
Tools = aiTools,
|
||||
RawRepresentationFactory = new Func<IChatClient, object>((_) => new ResponseCreationOptions() { StoredOutputEnabled = store })
|
||||
},
|
||||
});
|
||||
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent)
|
||||
{
|
||||
public Task DeleteAgentAsync(ChatClientAgent agent) =>
|
||||
// Chat Completion does not require/support deleting agents, so this is a no-op.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Task.CompletedTask;
|
||||
|
||||
public Task DeleteThreadAsync(AgentThread thread)
|
||||
{
|
||||
public Task DeleteThreadAsync(AgentThread thread) =>
|
||||
// Chat Completion does not require/support deleting threads, so this is a no-op.
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
Task.CompletedTask;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
@@ -103,8 +97,5 @@ public class OpenAIResponseFixture(bool store) : IChatClientAgentFixture
|
||||
this._agent = await this.CreateChatClientAgentAsync();
|
||||
}
|
||||
|
||||
public Task DisposeAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public Task DisposeAsync() => Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,8 @@ public class OpenAIResponseStoreTrueRunStreamingTests() : RunStreamingTests<Open
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
public class OpenAIResponseStoreFalseRunStreamingTests() : RunStreamingTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
@@ -20,8 +18,6 @@ public class OpenAIResponseStoreFalseRunStreamingTests() : RunStreamingTests<Ope
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
@@ -9,10 +9,8 @@ public class OpenAIResponseStoreTrueRunTests() : RunTests<OpenAIResponseFixture>
|
||||
{
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
public class OpenAIResponseStoreFalseRunTests() : RunTests<OpenAIResponseFixture>(() => new(store: false))
|
||||
@@ -20,8 +18,6 @@ public class OpenAIResponseStoreFalseRunTests() : RunTests<OpenAIResponseFixture
|
||||
private const string SkipReason = "OpenAIResponse does not support empty messages";
|
||||
|
||||
[Fact(Skip = SkipReason)]
|
||||
public override Task RunWithNoMessageDoesNotFailAsync()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
public override Task RunWithNoMessageDoesNotFailAsync() =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user