Enable more analyzers and various code tweaks/cleanup (#738)

This commit is contained in:
Stephen Toub
2025-09-18 01:48:25 +00:00
committed by GitHub
parent f3264966ff
commit 5e5761b288
326 changed files with 2402 additions and 3642 deletions
@@ -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);
}
}
@@ -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);
}
}
@@ -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;
@@ -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}")));
}
@@ -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]
@@ -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()
@@ -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);
@@ -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]));
}
@@ -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()
@@ -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;
}
}
@@ -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);
@@ -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; }
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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);
}
}
}
@@ -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]);
@@ -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;
@@ -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;
@@ -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);
}
}
@@ -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)
{
@@ -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) =>
@@ -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);
@@ -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);
@@ -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;
}
@@ -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,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);
}
@@ -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);
@@ -21,9 +21,7 @@ public class AgentRunOptionsTests
}
[Fact]
public void CloningConstructorThrowsIfNull()
{
public void CloningConstructorThrowsIfNull() =>
// Act & Assert
Assert.Throws<ArgumentNullException>(() => new AgentRunOptions(null!));
}
}
@@ -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);
@@ -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);
@@ -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;
}
}
@@ -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.
@@ -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();
@@ -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");
}
@@ -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();
@@ -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);
}
@@ -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(""));
}
}
@@ -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;
}
@@ -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;
}
@@ -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;
}
@@ -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;
}