From 6c89ec4a0816a7d709fa04d1c351451772e714d5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 17:54:30 +0000 Subject: [PATCH 1/7] Initial plan From 11b8dbc446aa74146d27402b30abc851a1e00008 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 22 Jan 2026 18:04:43 +0000 Subject: [PATCH 2/7] Add unit tests to improve coverage for Microsoft.Agents.AI.Abstractions Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> --- .../AIAgentMetadataTests.cs | 42 +++++ .../AIAgentTests.cs | 103 ++++++++++++ .../AIContextProviderTests.cs | 106 ++++++++++++ .../AgentResponseTests.cs | 87 ++++++++++ .../AgentResponseUpdateExtensionsTests.cs | 155 ++++++++++++++++++ .../AgentResponseUpdateTests.cs | 26 +++ .../ChatMessageStoreExtensionsTests.cs | 82 +++++++++ .../ChatMessageStoreTests.cs | 153 +++++++++++++++++ .../DelegatingAIAgentTests.cs | 21 +++ .../InMemoryChatMessageStoreTests.cs | 36 ++++ 10 files changed, 811 insertions(+) create mode 100644 dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs create mode 100644 dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs new file mode 100644 index 0000000000..f3575ba984 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft. All rights reserved. + +namespace Microsoft.Agents.AI.Abstractions.UnitTests; + +/// +/// Unit tests for the class. +/// +public class AIAgentMetadataTests +{ + [Fact] + public void Constructor_WithNoArguments_SetsProviderNameToNull() + { + // Arrange & Act + AIAgentMetadata metadata = new(); + + // Assert + Assert.Null(metadata.ProviderName); + } + + [Fact] + public void Constructor_WithProviderName_SetsProperty() + { + // Arrange + const string providerName = "TestProvider"; + + // Act + AIAgentMetadata metadata = new(providerName); + + // Assert + Assert.Equal(providerName, metadata.ProviderName); + } + + [Fact] + public void Constructor_WithNullProviderName_SetsProviderNameToNull() + { + // Arrange & Act + AIAgentMetadata metadata = new(providerName: null); + + // Assert + Assert.Null(metadata.ProviderName); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index 8d5f1b0b87..3869c8468e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -364,6 +364,74 @@ public void GetService_Generic_ReturnsNullForUnrelatedType() #endregion + #region Name and Description Property Tests + + /// + /// Verify that Name property returns the value from the derived class. + /// + [Fact] + public void Name_ReturnsValueFromDerivedClass() + { + // Arrange + var agent = new MockAgentWithName("TestAgentName", "TestAgentDescription"); + + // Act + string? name = agent.Name; + + // Assert + Assert.Equal("TestAgentName", name); + } + + /// + /// Verify that Description property returns the value from the derived class. + /// + [Fact] + public void Description_ReturnsValueFromDerivedClass() + { + // Arrange + var agent = new MockAgentWithName("TestAgentName", "TestAgentDescription"); + + // Act + string? description = agent.Description; + + // Assert + Assert.Equal("TestAgentDescription", description); + } + + /// + /// Verify that Name property returns null when not overridden. + /// + [Fact] + public void Name_ReturnsNullByDefault() + { + // Arrange + var agent = new MockAgent(); + + // Act + string? name = agent.Name; + + // Assert + Assert.Null(name); + } + + /// + /// Verify that Description property returns null when not overridden. + /// + [Fact] + public void Description_ReturnsNullByDefault() + { + // Arrange + var agent = new MockAgent(); + + // Act + string? description = agent.Description; + + // Assert + Assert.Null(description); + } + + #endregion + /// /// Typed mock thread. /// @@ -399,6 +467,41 @@ protected override IAsyncEnumerable RunCoreStreamingAsync( throw new NotImplementedException(); } + private sealed class MockAgentWithName : AIAgent + { + private readonly string? _name; + private readonly string? _description; + + public MockAgentWithName(string? name, string? description) + { + this._name = name; + this._description = description; + } + + public override string? Name => this._name; + public override string? Description => this._description; + + public override async ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public override async ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + protected override Task RunCoreAsync( + IEnumerable messages, + AgentThread? thread = null, + AgentRunOptions? options = null, + CancellationToken cancellationToken = default) => + throw new NotImplementedException(); + + protected override IAsyncEnumerable RunCoreStreamingAsync( + IEnumerable messages, + AgentThread? thread = null, + AgentRunOptions? options = null, + CancellationToken cancellationToken = default) => + throw new NotImplementedException(); + } + private static async IAsyncEnumerable ToAsyncEnumerableAsync(IEnumerable values) { await Task.Yield(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs index b287c8b304..b6aabd081e 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Threading; using System.Threading.Tasks; @@ -155,6 +156,111 @@ public void GetService_Generic_ReturnsNullForUnrelatedType() #endregion + #region InvokingContext Tests + + [Fact] + public void InvokingContext_RequestMessages_SetterThrowsForNull() + { + // Arrange + var messages = new ReadOnlyCollection([new(ChatRole.User, "Hello")]); + var context = new AIContextProvider.InvokingContext(messages); + + // Act & Assert + Assert.Throws(() => context.RequestMessages = null!); + } + + [Fact] + public void InvokingContext_RequestMessages_SetterRoundtrips() + { + // Arrange + var initialMessages = new ReadOnlyCollection([new(ChatRole.User, "Hello")]); + var newMessages = new List { new(ChatRole.User, "New message") }; + var context = new AIContextProvider.InvokingContext(initialMessages); + + // Act + context.RequestMessages = newMessages; + + // Assert + Assert.Same(newMessages, context.RequestMessages); + } + + #endregion + + #region InvokedContext Tests + + [Fact] + public void InvokedContext_RequestMessages_SetterThrowsForNull() + { + // Arrange + var messages = new ReadOnlyCollection([new(ChatRole.User, "Hello")]); + var context = new AIContextProvider.InvokedContext(messages, aiContextProviderMessages: null); + + // Act & Assert + Assert.Throws(() => context.RequestMessages = null!); + } + + [Fact] + public void InvokedContext_RequestMessages_SetterRoundtrips() + { + // Arrange + var initialMessages = new ReadOnlyCollection([new(ChatRole.User, "Hello")]); + var newMessages = new List { new(ChatRole.User, "New message") }; + var context = new AIContextProvider.InvokedContext(initialMessages, aiContextProviderMessages: null); + + // Act + context.RequestMessages = newMessages; + + // Assert + Assert.Same(newMessages, context.RequestMessages); + } + + [Fact] + public void InvokedContext_AIContextProviderMessages_Roundtrips() + { + // Arrange + var requestMessages = new ReadOnlyCollection([new(ChatRole.User, "Hello")]); + var aiContextMessages = new List { new(ChatRole.System, "AI context message") }; + var context = new AIContextProvider.InvokedContext(requestMessages, aiContextProviderMessages: null); + + // Act + context.AIContextProviderMessages = aiContextMessages; + + // Assert + Assert.Same(aiContextMessages, context.AIContextProviderMessages); + } + + [Fact] + public void InvokedContext_ResponseMessages_Roundtrips() + { + // Arrange + var requestMessages = new ReadOnlyCollection([new(ChatRole.User, "Hello")]); + var responseMessages = new List { new(ChatRole.Assistant, "Response message") }; + var context = new AIContextProvider.InvokedContext(requestMessages, aiContextProviderMessages: null); + + // Act + context.ResponseMessages = responseMessages; + + // Assert + Assert.Same(responseMessages, context.ResponseMessages); + } + + [Fact] + public void InvokedContext_InvokeException_Roundtrips() + { + // Arrange + var requestMessages = new ReadOnlyCollection([new(ChatRole.User, "Hello")]); + var exception = new InvalidOperationException("Test exception"); + var context = new AIContextProvider.InvokedContext(requestMessages, aiContextProviderMessages: null); + + // Act + context.InvokeException = exception; + + // Assert + Assert.Same(exception, context.InvokeException); + } + + #endregion + private sealed class TestAIContextProvider : AIContextProvider { public override ValueTask InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseTests.cs index 75bc90ca8e..87cdbf4f20 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseTests.cs @@ -346,4 +346,91 @@ public void TryParseAsStructuredOutputFailsWithIncorrectTypedJson() // Act & Assert. Assert.False(response.TryDeserialize(TestJsonSerializerContext.Default.Options, out _)); } + + [Fact] + public void UserInputRequests_ReturnsEmptyWhenNoMessages() + { + // Arrange + AgentResponse response = new(); + + // Act + IEnumerable requests = response.UserInputRequests; + + // Assert + Assert.Empty(requests); + } + + [Fact] + public void UserInputRequests_ReturnsEmptyWhenNoUserInputRequestContent() + { + // Arrange + AgentResponse response = new(new ChatMessage(ChatRole.Assistant, "Hello")); + + // Act + IEnumerable requests = response.UserInputRequests; + + // Assert + Assert.Empty(requests); + } + + [Fact] + public void ToAgentResponseUpdatesWithNoMessagesProducesEmptyArray() + { + // Arrange + AgentResponse response = new(); + + // Act + AgentResponseUpdate[] updates = response.ToAgentResponseUpdates(); + + // Assert + Assert.Empty(updates); + } + + [Fact] + public void ToAgentResponseUpdatesWithUsageOnlyProducesSingleUpdate() + { + // Arrange + AgentResponse response = new() + { + Usage = new UsageDetails { TotalTokenCount = 100 } + }; + + // Act + AgentResponseUpdate[] updates = response.ToAgentResponseUpdates(); + + // Assert + AgentResponseUpdate update = Assert.Single(updates); + UsageContent usageContent = Assert.IsType(update.Contents[0]); + Assert.Equal(100, usageContent.Details.TotalTokenCount); + } + + [Fact] + public void ToAgentResponseUpdatesWithAdditionalPropertiesOnlyProducesSingleUpdate() + { + // Arrange + AgentResponse response = new() + { + AdditionalProperties = new() { ["key"] = "value" } + }; + + // Act + AgentResponseUpdate[] updates = response.ToAgentResponseUpdates(); + + // Assert + AgentResponseUpdate update = Assert.Single(updates); + Assert.NotNull(update.AdditionalProperties); + Assert.Equal("value", update.AdditionalProperties!["key"]); + } + + [Fact] + public void Deserialize_ThrowsWhenDeserializationReturnsNull() + { + // Arrange + AgentResponse response = new(new ChatMessage(ChatRole.Assistant, "null")); + + // Act & Assert + InvalidOperationException exception = Assert.Throws( + () => response.Deserialize(TestJsonSerializerContext.Default.Options)); + Assert.Equal("The deserialized response is null.", exception.Message); + } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateExtensionsTests.cs index 2723ed081a..790298ddf9 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateExtensionsTests.cs @@ -299,6 +299,161 @@ public async Task ToAgentResponse_TimestampFoldingAsync(bool useAsync, string? t Assert.Equal(expected, response.CreatedAt); } + #region AsChatResponse Tests + + [Fact] + public void AsChatResponse_WithNullArgument_ThrowsArgumentNullException() + { + // Arrange & Act & Assert + Assert.Throws("response", () => ((AgentResponse)null!).AsChatResponse()); + } + + [Fact] + public void AsChatResponse_WithRawRepresentationAsChatResponse_ReturnsSameInstance() + { + // Arrange + ChatResponse originalChatResponse = new() + { + ResponseId = "original-response", + Messages = [new ChatMessage(ChatRole.Assistant, "Hello")] + }; + AgentResponse agentResponse = new(originalChatResponse); + + // Act + ChatResponse result = agentResponse.AsChatResponse(); + + // Assert + Assert.Same(originalChatResponse, result); + } + + [Fact] + public void AsChatResponse_WithoutRawRepresentation_CreatesNewChatResponse() + { + // Arrange + AgentResponse agentResponse = new(new ChatMessage(ChatRole.Assistant, "Test message")) + { + ResponseId = "test-response-id", + CreatedAt = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero), + Usage = new UsageDetails { TotalTokenCount = 50 }, + AdditionalProperties = new() { ["key"] = "value" }, + ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }), + }; + + // Act + ChatResponse result = agentResponse.AsChatResponse(); + + // Assert + Assert.NotNull(result); + Assert.Equal("test-response-id", result.ResponseId); + Assert.Equal(new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero), result.CreatedAt); + Assert.Same(agentResponse.Messages, result.Messages); + Assert.Same(agentResponse, result.RawRepresentation); + Assert.Same(agentResponse.Usage, result.Usage); + Assert.Same(agentResponse.AdditionalProperties, result.AdditionalProperties); + Assert.Equal(agentResponse.ContinuationToken, result.ContinuationToken); + } + + #endregion + + #region AsChatResponseUpdate Tests + + [Fact] + public void AsChatResponseUpdate_WithNullArgument_ThrowsArgumentNullException() + { + // Arrange & Act & Assert + Assert.Throws("responseUpdate", () => ((AgentResponseUpdate)null!).AsChatResponseUpdate()); + } + + [Fact] + public void AsChatResponseUpdate_WithRawRepresentationAsChatResponseUpdate_ReturnsSameInstance() + { + // Arrange + ChatResponseUpdate originalChatResponseUpdate = new() + { + ResponseId = "original-update", + Contents = [new TextContent("Hello")] + }; + AgentResponseUpdate agentResponseUpdate = new(originalChatResponseUpdate); + + // Act + ChatResponseUpdate result = agentResponseUpdate.AsChatResponseUpdate(); + + // Assert + Assert.Same(originalChatResponseUpdate, result); + } + + [Fact] + public void AsChatResponseUpdate_WithoutRawRepresentation_CreatesNewChatResponseUpdate() + { + // Arrange + AgentResponseUpdate agentResponseUpdate = new(ChatRole.Assistant, "Test") + { + AuthorName = "TestAuthor", + ResponseId = "update-id", + MessageId = "message-id", + CreatedAt = new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero), + AdditionalProperties = new() { ["key"] = "value" }, + ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }), + }; + + // Act + ChatResponseUpdate result = agentResponseUpdate.AsChatResponseUpdate(); + + // Assert + Assert.NotNull(result); + Assert.Equal("TestAuthor", result.AuthorName); + Assert.Equal("update-id", result.ResponseId); + Assert.Equal("message-id", result.MessageId); + Assert.Equal(new DateTimeOffset(2024, 1, 1, 12, 0, 0, TimeSpan.Zero), result.CreatedAt); + Assert.Equal(ChatRole.Assistant, result.Role); + Assert.Same(agentResponseUpdate.Contents, result.Contents); + Assert.Same(agentResponseUpdate, result.RawRepresentation); + Assert.Same(agentResponseUpdate.AdditionalProperties, result.AdditionalProperties); + Assert.Equal(agentResponseUpdate.ContinuationToken, result.ContinuationToken); + } + + #endregion + + #region AsChatResponseUpdatesAsync Tests + + [Fact] + public async Task AsChatResponseUpdatesAsync_WithNullArgument_ThrowsArgumentNullExceptionAsync() + { + // Arrange & Act & Assert + await Assert.ThrowsAsync("responseUpdates", async () => + { + await foreach (ChatResponseUpdate _ in ((IAsyncEnumerable)null!).AsChatResponseUpdatesAsync()) + { + // Do nothing + } + }); + } + + [Fact] + public async Task AsChatResponseUpdatesAsync_ConvertsUpdatesAsync() + { + // Arrange + AgentResponseUpdate[] updates = + [ + new(ChatRole.Assistant, "First"), + new(ChatRole.Assistant, "Second"), + ]; + + // Act + List results = []; + await foreach (ChatResponseUpdate update in YieldAsync(updates).AsChatResponseUpdatesAsync()) + { + results.Add(update); + } + + // Assert + Assert.Equal(2, results.Count); + Assert.Equal("First", Assert.IsType(results[0].Contents[0]).Text); + Assert.Equal("Second", Assert.IsType(results[1].Contents[0]).Text); + } + + #endregion + private static async IAsyncEnumerable YieldAsync(IEnumerable updates) { foreach (AgentResponseUpdate update in updates) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateTests.cs index 7fda5f680b..1b42188c92 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentResponseUpdateTests.cs @@ -199,4 +199,30 @@ public void JsonSerializationRoundtrips() Assert.NotNull(result.ContinuationToken); Assert.Equivalent(ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }), result.ContinuationToken); } + + [Fact] + public void UserInputRequests_ReturnsEmptyWhenNoContents() + { + // Arrange + AgentResponseUpdate update = new(); + + // Act + IEnumerable requests = update.UserInputRequests; + + // Assert + Assert.Empty(requests); + } + + [Fact] + public void UserInputRequests_ReturnsEmptyWhenNoUserInputRequestContent() + { + // Arrange + AgentResponseUpdate update = new(ChatRole.Assistant, "Hello"); + + // Act + IEnumerable requests = update.UserInputRequests; + + // Assert + Assert.Empty(requests); + } } diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs new file mode 100644 index 0000000000..2a898ec3d8 --- /dev/null +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft. All rights reserved. + +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.AI; +using Moq; + +namespace Microsoft.Agents.AI.Abstractions.UnitTests; + +/// +/// Unit tests for the class. +/// +public class ChatMessageStoreExtensionsTests +{ + [Fact] + public async Task WithMessageFilters_AppliesBothFiltersAsync() + { + // Arrange + var innerStoreMock = new Mock(); + var innerMessages = new List + { + new(ChatRole.User, "Hello"), + new(ChatRole.Assistant, "Hi there!") + }; + var invokingContext = new ChatMessageStore.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); + + innerStoreMock + .Setup(s => s.InvokingAsync(invokingContext, It.IsAny())) + .ReturnsAsync(innerMessages); + + IEnumerable InvokingFilter(IEnumerable msgs) => + msgs.Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")); + + ChatMessageStore.InvokedContext InvokedFilter(ChatMessageStore.InvokedContext ctx) + { + ctx.AIContextProviderMessages = null; + return ctx; + } + + // Act + ChatMessageStore filteredStore = innerStoreMock.Object.WithMessageFilters(InvokingFilter, InvokedFilter); + IEnumerable result = await filteredStore.InvokingAsync(invokingContext, CancellationToken.None); + + // Assert + Assert.NotNull(filteredStore); + List resultList = result.ToList(); + Assert.Equal(2, resultList.Count); + Assert.Equal("[FILTERED] Hello", resultList[0].Text); + Assert.Equal("[FILTERED] Hi there!", resultList[1].Text); + } + + [Fact] + public async Task WithAIContextProviderMessageRemoval_RemovesAIContextProviderMessagesAsync() + { + // Arrange + var innerStore = new InMemoryChatMessageStore(); + var requestMessages = new List { new(ChatRole.User, "Hello") }; + var chatMessageStoreMessages = new List { new(ChatRole.System, "System") }; + var aiContextProviderMessages = new List { new(ChatRole.System, "AI Context Provider Message") }; + var responseMessages = new List { new(ChatRole.Assistant, "Response") }; + + var context = new ChatMessageStore.InvokedContext(requestMessages, chatMessageStoreMessages) + { + AIContextProviderMessages = aiContextProviderMessages, + ResponseMessages = responseMessages + }; + + // Act + ChatMessageStore filteredStore = innerStore.WithAIContextProviderMessageRemoval(); + await filteredStore.InvokedAsync(context, CancellationToken.None); + + // Assert + Assert.NotNull(filteredStore); + + // Verify that AI context provider messages are not added to the store + Assert.Equal(2, innerStore.Count); + Assert.Equal("Hello", innerStore[0].Text); + Assert.Equal("Response", innerStore[1].Text); + } +} diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreTests.cs index 883941458c..e007af4b05 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreTests.cs @@ -76,6 +76,159 @@ public void GetService_Generic_ReturnsNullForUnrelatedType() #endregion + #region InvokingContext Tests + + [Fact] + public void InvokingContext_Constructor_ThrowsForNullMessages() + { + // Arrange & Act & Assert + Assert.Throws(() => new ChatMessageStore.InvokingContext(null!)); + } + + [Fact] + public void InvokingContext_RequestMessages_SetterThrowsForNull() + { + // Arrange + var messages = new List { new(ChatRole.User, "Hello") }; + var context = new ChatMessageStore.InvokingContext(messages); + + // Act & Assert + Assert.Throws(() => context.RequestMessages = null!); + } + + [Fact] + public void InvokingContext_RequestMessages_SetterRoundtrips() + { + // Arrange + var initialMessages = new List { new(ChatRole.User, "Hello") }; + var newMessages = new List { new(ChatRole.User, "New message") }; + var context = new ChatMessageStore.InvokingContext(initialMessages); + + // Act + context.RequestMessages = newMessages; + + // Assert + Assert.Same(newMessages, context.RequestMessages); + } + + #endregion + + #region InvokedContext Tests + + [Fact] + public void InvokedContext_Constructor_ThrowsForNullRequestMessages() + { + // Arrange & Act & Assert + Assert.Throws(() => new ChatMessageStore.InvokedContext(null!, [])); + } + + [Fact] + public void InvokedContext_Constructor_ThrowsForNullChatMessageStoreMessages() + { + // Arrange & Act & Assert + var messages = new List { new(ChatRole.User, "Hello") }; + Assert.Throws(() => new ChatMessageStore.InvokedContext(messages, null!)); + } + + [Fact] + public void InvokedContext_RequestMessages_SetterThrowsForNull() + { + // Arrange + var requestMessages = new List { new(ChatRole.User, "Hello") }; + var context = new ChatMessageStore.InvokedContext(requestMessages, []); + + // Act & Assert + Assert.Throws(() => context.RequestMessages = null!); + } + + [Fact] + public void InvokedContext_RequestMessages_SetterRoundtrips() + { + // Arrange + var initialMessages = new List { new(ChatRole.User, "Hello") }; + var newMessages = new List { new(ChatRole.User, "New message") }; + var context = new ChatMessageStore.InvokedContext(initialMessages, []); + + // Act + context.RequestMessages = newMessages; + + // Assert + Assert.Same(newMessages, context.RequestMessages); + } + + [Fact] + public void InvokedContext_ChatMessageStoreMessages_SetterThrowsForNull() + { + // Arrange + var requestMessages = new List { new(ChatRole.User, "Hello") }; + var context = new ChatMessageStore.InvokedContext(requestMessages, []); + + // Act & Assert + Assert.Throws(() => context.ChatMessageStoreMessages = null!); + } + + [Fact] + public void InvokedContext_ChatMessageStoreMessages_SetterRoundtrips() + { + // Arrange + var requestMessages = new List { new(ChatRole.User, "Hello") }; + var newStoreMessages = new List { new(ChatRole.System, "System message") }; + var context = new ChatMessageStore.InvokedContext(requestMessages, []); + + // Act + context.ChatMessageStoreMessages = newStoreMessages; + + // Assert + Assert.Same(newStoreMessages, context.ChatMessageStoreMessages); + } + + [Fact] + public void InvokedContext_AIContextProviderMessages_Roundtrips() + { + // Arrange + var requestMessages = new List { new(ChatRole.User, "Hello") }; + var aiContextMessages = new List { new(ChatRole.System, "AI context message") }; + var context = new ChatMessageStore.InvokedContext(requestMessages, []); + + // Act + context.AIContextProviderMessages = aiContextMessages; + + // Assert + Assert.Same(aiContextMessages, context.AIContextProviderMessages); + } + + [Fact] + public void InvokedContext_ResponseMessages_Roundtrips() + { + // Arrange + var requestMessages = new List { new(ChatRole.User, "Hello") }; + var responseMessages = new List { new(ChatRole.Assistant, "Response message") }; + var context = new ChatMessageStore.InvokedContext(requestMessages, []); + + // Act + context.ResponseMessages = responseMessages; + + // Assert + Assert.Same(responseMessages, context.ResponseMessages); + } + + [Fact] + public void InvokedContext_InvokeException_Roundtrips() + { + // Arrange + var requestMessages = new List { new(ChatRole.User, "Hello") }; + var exception = new InvalidOperationException("Test exception"); + var context = new ChatMessageStore.InvokedContext(requestMessages, []); + + // Act + context.InvokeException = exception; + + // Assert + Assert.Same(exception, context.InvokeException); + } + + #endregion + private sealed class TestChatMessageStore : ChatMessageStore { public override ValueTask> InvokingAsync(InvokingContext context, CancellationToken cancellationToken = default) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs index 8055a95f3a..096f8c0642 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; @@ -145,6 +146,26 @@ public async Task GetNewThreadAsync_DelegatesToInnerAgentAsync() this._innerAgentMock.Verify(x => x.GetNewThreadAsync(), Times.Once); } + /// + /// Verify that DeserializeThreadAsync delegates to inner agent. + /// + [Fact] + public async Task DeserializeThreadAsync_DelegatesToInnerAgentAsync() + { + // Arrange + var serializedThread = JsonSerializer.SerializeToElement("test-thread-id", TestJsonSerializerContext.Default.String); + this._innerAgentMock + .Setup(x => x.DeserializeThreadAsync(It.IsAny(), null, It.IsAny())) + .ReturnsAsync(this._testThread); + + // Act + var thread = await this._delegatingAgent.DeserializeThreadAsync(serializedThread); + + // Assert + Assert.Same(this._testThread, thread); + this._innerAgentMock.Verify(x => x.DeserializeThreadAsync(It.IsAny(), null, It.IsAny()), Times.Once); + } + /// /// Verify that RunAsync delegates to inner agent with correct parameters. /// diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs index 43bfacca79..fdb4468b51 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/InMemoryChatMessageStoreTests.cs @@ -614,6 +614,42 @@ public async Task GetMessagesAsync_WithReducer_ButWrongTrigger_DoesNotInvokeRedu reducerMock.Verify(r => r.ReduceAsync(It.IsAny>(), It.IsAny()), Times.Never); } + [Fact] + public async Task InvokedAsync_WithException_DoesNotAddMessagesAsync() + { + // Arrange + var store = new InMemoryChatMessageStore(); + var requestMessages = new List + { + new(ChatRole.User, "Hello") + }; + var responseMessages = new List + { + new(ChatRole.Assistant, "Hi there!") + }; + var context = new ChatMessageStore.InvokedContext(requestMessages, []) + { + ResponseMessages = responseMessages, + InvokeException = new InvalidOperationException("Test exception") + }; + + // Act + await store.InvokedAsync(context, CancellationToken.None); + + // Assert + Assert.Empty(store); + } + + [Fact] + public async Task InvokingAsync_WithNullContext_ThrowsArgumentNullExceptionAsync() + { + // Arrange + var store = new InMemoryChatMessageStore(); + + // Act & Assert + await Assert.ThrowsAsync(() => store.InvokingAsync(null!, CancellationToken.None).AsTask()); + } + public class TestAIContent(string testData) : AIContent { public string TestData => testData; From 5b3ff586a2eaad834d51105258bc39a4c155c569 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 23 Jan 2026 12:02:25 +0000 Subject: [PATCH 3/7] Fix file encoding and naming rule violation in new test files Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> --- .../AIAgentMetadataTests.cs | 10 +++++----- .../ChatMessageStoreExtensionsTests.cs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs index f3575ba984..764f7f2122 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentMetadataTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. namespace Microsoft.Agents.AI.Abstractions.UnitTests; @@ -21,20 +21,20 @@ public void Constructor_WithNoArguments_SetsProviderNameToNull() public void Constructor_WithProviderName_SetsProperty() { // Arrange - const string providerName = "TestProvider"; + const string ProviderName = "TestProvider"; // Act - AIAgentMetadata metadata = new(providerName); + AIAgentMetadata metadata = new(ProviderName); // Assert - Assert.Equal(providerName, metadata.ProviderName); + Assert.Equal(ProviderName, metadata.ProviderName); } [Fact] public void Constructor_WithNullProviderName_SetsProviderNameToNull() { // Arrange & Act - AIAgentMetadata metadata = new(providerName: null); + AIAgentMetadata metadata = new(null); // Assert Assert.Null(metadata.ProviderName); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs index 2a898ec3d8..edba0475a6 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. using System.Collections.Generic; using System.Linq; From b1e4643ba41ff89587d8b8f55a5a73c12efdcfd5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 26 Jan 2026 12:57:16 +0000 Subject: [PATCH 4/7] Remove ChatMessageStoreExtensionsTests.cs to avoid duplication with Wesley's work Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com> --- .../ChatMessageStoreExtensionsTests.cs | 82 ------------------- 1 file changed, 82 deletions(-) delete mode 100644 dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs deleted file mode 100644 index edba0475a6..0000000000 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ChatMessageStoreExtensionsTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. - -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.AI; -using Moq; - -namespace Microsoft.Agents.AI.Abstractions.UnitTests; - -/// -/// Unit tests for the class. -/// -public class ChatMessageStoreExtensionsTests -{ - [Fact] - public async Task WithMessageFilters_AppliesBothFiltersAsync() - { - // Arrange - var innerStoreMock = new Mock(); - var innerMessages = new List - { - new(ChatRole.User, "Hello"), - new(ChatRole.Assistant, "Hi there!") - }; - var invokingContext = new ChatMessageStore.InvokingContext([new ChatMessage(ChatRole.User, "Test")]); - - innerStoreMock - .Setup(s => s.InvokingAsync(invokingContext, It.IsAny())) - .ReturnsAsync(innerMessages); - - IEnumerable InvokingFilter(IEnumerable msgs) => - msgs.Select(m => new ChatMessage(m.Role, $"[FILTERED] {m.Text}")); - - ChatMessageStore.InvokedContext InvokedFilter(ChatMessageStore.InvokedContext ctx) - { - ctx.AIContextProviderMessages = null; - return ctx; - } - - // Act - ChatMessageStore filteredStore = innerStoreMock.Object.WithMessageFilters(InvokingFilter, InvokedFilter); - IEnumerable result = await filteredStore.InvokingAsync(invokingContext, CancellationToken.None); - - // Assert - Assert.NotNull(filteredStore); - List resultList = result.ToList(); - Assert.Equal(2, resultList.Count); - Assert.Equal("[FILTERED] Hello", resultList[0].Text); - Assert.Equal("[FILTERED] Hi there!", resultList[1].Text); - } - - [Fact] - public async Task WithAIContextProviderMessageRemoval_RemovesAIContextProviderMessagesAsync() - { - // Arrange - var innerStore = new InMemoryChatMessageStore(); - var requestMessages = new List { new(ChatRole.User, "Hello") }; - var chatMessageStoreMessages = new List { new(ChatRole.System, "System") }; - var aiContextProviderMessages = new List { new(ChatRole.System, "AI Context Provider Message") }; - var responseMessages = new List { new(ChatRole.Assistant, "Response") }; - - var context = new ChatMessageStore.InvokedContext(requestMessages, chatMessageStoreMessages) - { - AIContextProviderMessages = aiContextProviderMessages, - ResponseMessages = responseMessages - }; - - // Act - ChatMessageStore filteredStore = innerStore.WithAIContextProviderMessageRemoval(); - await filteredStore.InvokedAsync(context, CancellationToken.None); - - // Assert - Assert.NotNull(filteredStore); - - // Verify that AI context provider messages are not added to the store - Assert.Equal(2, innerStore.Count); - Assert.Equal("Hello", innerStore[0].Text); - Assert.Equal("Response", innerStore[1].Text); - } -} From 677af00e069bb047c2eae47fd42cb2b2cec8642d Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Wed, 28 Jan 2026 19:05:15 +0000 Subject: [PATCH 5/7] Fix AgentThread to AgentSession rename in unit tests Update MockAgentWithName in AIAgentTests.cs and DelegatingAIAgentTests.cs to use the renamed AgentSession class and corresponding methods: - AgentThread -> AgentSession - GetNewThreadAsync -> GetNewSessionAsync - DeserializeThreadAsync -> DeserializeSessionAsync - thread parameter -> session parameter --- .../AIAgentTests.cs | 8 ++++---- .../DelegatingAIAgentTests.cs | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index beb20cef5b..b2bbb5bb53 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -481,22 +481,22 @@ public MockAgentWithName(string? name, string? description) public override string? Name => this._name; public override string? Description => this._description; - public override async ValueTask GetNewThreadAsync(CancellationToken cancellationToken = default) + public override async ValueTask GetNewSessionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public override async ValueTask DeserializeThreadAsync(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + public override async ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override Task RunCoreAsync( IEnumerable messages, - AgentThread? thread = null, + AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); protected override IAsyncEnumerable RunCoreStreamingAsync( IEnumerable messages, - AgentThread? thread = null, + AgentSession? session = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default) => throw new NotImplementedException(); diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs index 051a6eaeb2..78099aa25a 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/DelegatingAIAgentTests.cs @@ -147,23 +147,23 @@ public async Task GetNewSessionAsync_DelegatesToInnerAgentAsync() } /// - /// Verify that DeserializeThreadAsync delegates to inner agent. + /// Verify that DeserializeSessionAsync delegates to inner agent. /// [Fact] - public async Task DeserializeThreadAsync_DelegatesToInnerAgentAsync() + public async Task DeserializeSessionAsync_DelegatesToInnerAgentAsync() { // Arrange - var serializedThread = JsonSerializer.SerializeToElement("test-thread-id", TestJsonSerializerContext.Default.String); + var serializedSession = JsonSerializer.SerializeToElement("test-session-id", TestJsonSerializerContext.Default.String); this._innerAgentMock - .Setup(x => x.DeserializeThreadAsync(It.IsAny(), null, It.IsAny())) - .ReturnsAsync(this._testThread); + .Setup(x => x.DeserializeSessionAsync(It.IsAny(), null, It.IsAny())) + .ReturnsAsync(this._testSession); // Act - var thread = await this._delegatingAgent.DeserializeThreadAsync(serializedThread); + var session = await this._delegatingAgent.DeserializeSessionAsync(serializedSession); // Assert - Assert.Same(this._testThread, thread); - this._innerAgentMock.Verify(x => x.DeserializeThreadAsync(It.IsAny(), null, It.IsAny()), Times.Once); + Assert.Same(this._testSession, session); + this._innerAgentMock.Verify(x => x.DeserializeSessionAsync(It.IsAny(), null, It.IsAny()), Times.Once); } /// From aedddc8bd94e1aa1ab3d7e6e8d6c908bb52c4de2 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Wed, 4 Feb 2026 10:52:14 +0000 Subject: [PATCH 6/7] Fix: Rename GetNewSessionAsync to CreateSessionAsync to match API changes --- .../Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index a72a1fc16e..df803fba69 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -481,7 +481,7 @@ public MockAgentWithName(string? name, string? description) public override string? Name => this._name; public override string? Description => this._description; - public override async ValueTask GetNewSessionAsync(CancellationToken cancellationToken = default) + public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); public override async ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) From 1cb4aa9b42839d5e5d810a1a90a2418dcbec8017 Mon Sep 17 00:00:00 2001 From: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com> Date: Wed, 4 Feb 2026 17:02:12 +0000 Subject: [PATCH 7/7] Fix: Add SerializeSession override and remove async from DeserializeSessionAsync --- .../AIAgentTests.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs index 5eb602b3aa..c65cb66e59 100644 --- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs +++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs @@ -487,7 +487,10 @@ public MockAgentWithName(string? name, string? description) public override ValueTask CreateSessionAsync(CancellationToken cancellationToken = default) => throw new NotImplementedException(); - public override async ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + public override ValueTask DeserializeSessionAsync(JsonElement serializedSession, JsonSerializerOptions? jsonSerializerOptions = null, CancellationToken cancellationToken = default) + => throw new NotImplementedException(); + + public override JsonElement SerializeSession(AgentSession session, JsonSerializerOptions? jsonSerializerOptions = null) => throw new NotImplementedException(); protected override Task RunCoreAsync(