// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.AI; using Microsoft.Extensions.Logging; using Microsoft.Extensions.VectorData; using Moq; namespace Microsoft.Agents.AI.Memory.UnitTests; /// /// Contains unit tests for the class. /// public class ChatHistoryMemoryProviderTests { private static readonly AIAgent s_mockAgent = new Mock().Object; private readonly Mock> _loggerMock; private readonly Mock _loggerFactoryMock; private readonly Mock _vectorStoreMock; private readonly Mock>> _vectorStoreCollectionMock; private const string TestCollectionName = "testcollection"; public ChatHistoryMemoryProviderTests() { this._loggerMock = new(); this._loggerFactoryMock = new(); this._loggerFactoryMock .Setup(f => f.CreateLogger(It.IsAny())) .Returns(this._loggerMock.Object); this._loggerFactoryMock .Setup(f => f.CreateLogger(typeof(ChatHistoryMemoryProvider).FullName!)) .Returns(this._loggerMock.Object); this._loggerMock .Setup(f => f.IsEnabled(It.IsAny())) .Returns(true); this._vectorStoreCollectionMock = new(MockBehavior.Strict); this._vectorStoreMock = new(MockBehavior.Strict); this._vectorStoreCollectionMock .Setup(c => c.EnsureCollectionExistsAsync(It.IsAny())) .Returns(Task.CompletedTask); this._vectorStoreMock .Setup(vs => vs.GetDynamicCollection( It.IsAny(), It.IsAny())) .Returns(this._vectorStoreCollectionMock.Object); } [Fact] public void StateKey_ReturnsDefaultKey_WhenNoOptionsProvided() { // Arrange & Act var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" })); // Assert Assert.Equal("ChatHistoryMemoryProvider", provider.StateKey); } [Fact] public void StateKey_ReturnsCustomKey_WhenSetViaOptions() { // Arrange & Act var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }), new ChatHistoryMemoryProviderOptions { StateKey = "custom-key" }); // Assert Assert.Equal("custom-key", provider.StateKey); } [Fact] public void Constructor_Throws_ForNullVectorStore() { // Act & Assert Assert.Throws(() => new ChatHistoryMemoryProvider( null!, "testcollection", 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }))); } [Fact] public void Constructor_Throws_ForNullCollectionName() { // Act & Assert Assert.Throws(() => new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, null!, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }))); } [Fact] public void Constructor_Throws_ForNullStateInitializer() { // Act & Assert Assert.Throws(() => new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, "testcollection", 1, null!)); } [Fact] public void Constructor_Throws_ForInvalidVectorDimensions() { // Act & Assert Assert.Throws(() => new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, "testcollection", 0, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }))); Assert.Throws(() => new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, "testcollection", -5, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }))); } #region InvokedAsync Tests [Fact] public async Task InvokedAsync_UpsertsMessages_ToCollectionAsync() { // Arrange var stored = new List>(); this._vectorStoreCollectionMock .Setup(c => c.UpsertAsync(It.IsAny>>(), It.IsAny())) .Callback>, CancellationToken>((items, ct) => { if (items != null) { stored.AddRange(items); } }) .Returns(Task.CompletedTask); var storeScope = new ChatHistoryMemoryProviderScope { ApplicationId = "app1", AgentId = "agent1", SessionId = "session1", UserId = "user1" }; var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(storeScope)); var requestMsgWithValues = new ChatMessage(ChatRole.User, "request text") { MessageId = "req-1", AuthorName = "user1", CreatedAt = new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.Zero) }; var requestMsgWithNulls = new ChatMessage(ChatRole.User, "request text nulls"); var responseMsg = new ChatMessage(ChatRole.Assistant, "response text") { MessageId = "resp-1", AuthorName = "assistant" }; var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsgWithValues, requestMsgWithNulls], [responseMsg]); // Act await provider.InvokedAsync(invokedContext, CancellationToken.None); // Assert this._vectorStoreCollectionMock.Verify( m => m.EnsureCollectionExistsAsync(It.IsAny()), Times.Once); Assert.Equal(3, stored.Count); Assert.Equal("req-1", stored[0]["MessageId"]); Assert.Equal("request text", stored[0]["Content"]); Assert.Equal("user1", stored[0]["AuthorName"]); Assert.Equal(ChatRole.User.ToString(), stored[0]["Role"]); Assert.Equal("2000-01-01T00:00:00.0000000+00:00", stored[0]["CreatedAt"]); Assert.Equal("app1", stored[0]["ApplicationId"]); Assert.Equal("agent1", stored[0]["AgentId"]); Assert.Equal("session1", stored[0]["SessionId"]); Assert.Equal("user1", stored[0]["UserId"]); Assert.Null(stored[1]["MessageId"]); Assert.Equal("request text nulls", stored[1]["Content"]); Assert.Null(stored[1]["AuthorName"]); Assert.Equal(ChatRole.User.ToString(), stored[1]["Role"]); Assert.Equal("app1", stored[1]["ApplicationId"]); Assert.Equal("agent1", stored[1]["AgentId"]); Assert.Equal("session1", stored[1]["SessionId"]); Assert.Equal("user1", stored[1]["UserId"]); Assert.Equal("resp-1", stored[2]["MessageId"]); Assert.Equal("response text", stored[2]["Content"]); Assert.Equal("assistant", stored[2]["AuthorName"]); Assert.Equal(ChatRole.Assistant.ToString(), stored[2]["Role"]); Assert.Equal("app1", stored[2]["ApplicationId"]); Assert.Equal("agent1", stored[2]["AgentId"]); Assert.Equal("session1", stored[2]["SessionId"]); Assert.Equal("user1", stored[2]["UserId"]); } [Fact] public async Task InvokedAsync_DoesNotUpsertMessages_WhenInvokeFailedAsync() { // Arrange this._vectorStoreCollectionMock .Setup(c => c.UpsertAsync(It.IsAny>>(), It.IsAny())) .Returns(Task.CompletedTask); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" })); var requestMsg = new ChatMessage(ChatRole.User, "request text") { MessageId = "req-1" }; var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg], new InvalidOperationException("Invoke failed")); // Act await provider.InvokedAsync(invokedContext, CancellationToken.None); // Assert this._vectorStoreCollectionMock.Verify( c => c.UpsertAsync(It.IsAny>>(), It.IsAny()), Times.Never); } [Fact] public async Task InvokedAsync_DoesNotThrow_WhenUpsertThrowsAsync() { // Arrange this._vectorStoreCollectionMock .Setup(c => c.UpsertAsync(It.IsAny>>(), It.IsAny())) .ThrowsAsync(new InvalidOperationException("Upsert failed")); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }), loggerFactory: this._loggerFactoryMock.Object); var requestMsg = new ChatMessage(ChatRole.User, "request text") { MessageId = "req-1" }; var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg], []); // Act await provider.InvokedAsync(invokedContext, CancellationToken.None); // Assert this._loggerMock.Verify( l => l.Log( LogLevel.Error, It.IsAny(), It.Is((v, t) => v.ToString()!.Contains("ChatHistoryMemoryProvider: Failed to add messages to chat history vector store due to error")), It.IsAny(), It.IsAny>()), Times.Once); } [Theory] [InlineData(false, false, 0)] [InlineData(true, false, 0)] [InlineData(false, true, 2)] [InlineData(true, true, 2)] public async Task InvokedAsync_LogsUserIdBasedOnEnableSensitiveTelemetryDataAsync(bool enableSensitiveTelemetryData, bool requestThrows, int expectedLogInvocations) { // Arrange var options = new ChatHistoryMemoryProviderOptions { EnableSensitiveTelemetryData = enableSensitiveTelemetryData }; if (requestThrows) { this._vectorStoreCollectionMock .Setup(c => c.UpsertAsync(It.IsAny>>(), It.IsAny())) .ThrowsAsync(new InvalidOperationException("Upsert failed")); } else { this._vectorStoreCollectionMock .Setup(c => c.UpsertAsync(It.IsAny>>(), It.IsAny())) .Returns(Task.CompletedTask); } var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "user1" }), options: options, loggerFactory: this._loggerFactoryMock.Object); var requestMsg = new ChatMessage(ChatRole.User, "request text"); var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), [requestMsg], []); // Act await provider.InvokedAsync(invokedContext, CancellationToken.None); // Assert Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count); foreach (var logInvocation in this._loggerMock.Invocations) { if (logInvocation.Method.Name == nameof(ILogger.IsEnabled)) { continue; } var state = Assert.IsType>>(logInvocation.Arguments[2], exactMatch: false); var userIdValue = state.First(kvp => kvp.Key == "UserId").Value; Assert.Equal(enableSensitiveTelemetryData ? "user1" : "", userIdValue); } } #endregion #region InvokingAsync Tests [Fact] public async Task InvokedAsync_SearchesVectorStoreAsync() { // Arrange var providerOptions = new ChatHistoryMemoryProviderOptions { SearchTime = ChatHistoryMemoryProviderOptions.SearchBehavior.BeforeAIInvoke, MaxResults = 2, ContextPrompt = "Here is the relevant chat history:\n" }; var storedItems = new List>> { new( new Dictionary { ["MessageId"] = "msg-1", ["Content"] = "First stored message", ["Role"] = ChatRole.User.ToString(), ["CreatedAt"] = "2023-01-01T00:00:00.0000000+00:00" }, 0.9f), new( new Dictionary { ["MessageId"] = "msg-2", ["Content"] = "Second stored message", ["Role"] = ChatRole.User.ToString(), ["CreatedAt"] = "2023-01-02T00:00:00.0000000+00:00" }, 0.8f) }; this._vectorStoreCollectionMock .Setup(c => c.SearchAsync( It.IsAny(), It.IsAny(), It.IsAny>>(), It.IsAny())) .Returns(ToAsyncEnumerableAsync(storedItems)); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }), options: providerOptions); var requestMsg = new ChatMessage(ChatRole.User, "requesting relevant history"); var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, new TestAgentSession(), new AIContext { Messages = new List { requestMsg } }); // Act var aiContext = await provider.InvokingAsync(invokingContext, CancellationToken.None); // Assert this._vectorStoreCollectionMock.Verify( c => c.SearchAsync( It.Is(s => s == "requesting relevant history"), 2, It.IsAny>>(), It.IsAny()), Times.Once); Assert.NotNull(aiContext.Messages); var messages = aiContext.Messages.ToList(); Assert.Equal(2, messages.Count); Assert.Equal(AgentRequestMessageSourceType.External, messages[0].GetAgentRequestMessageSourceType()); Assert.Equal(AgentRequestMessageSourceType.AIContextProvider, messages[1].GetAgentRequestMessageSourceType()); } [Fact] public async Task InvokedAsync_CreatesFilter_WhenSearchScopeProvidedAsync() { // Arrange var providerOptions = new ChatHistoryMemoryProviderOptions { SearchTime = ChatHistoryMemoryProviderOptions.SearchBehavior.BeforeAIInvoke, MaxResults = 2, ContextPrompt = "Here is the relevant chat history:\n" }; var searchScope = new ChatHistoryMemoryProviderScope { ApplicationId = "app1", AgentId = "agent1", SessionId = "session1", UserId = "user1" }; this._vectorStoreCollectionMock .Setup(c => c.SearchAsync( It.IsAny(), It.IsAny(), It.IsAny>>(), It.IsAny())) .Callback((string query, int maxResults, VectorSearchOptions> options, CancellationToken ct) => { // Verify that the filter was created correctly const string ExpectedFilter = "x => ((((x.ApplicationId == value(Microsoft.Agents.AI.VectorDataMemory.ChatHistoryMemoryProvider+<>c__DisplayClass20_0).applicationId) AndAlso (x.AgentId == value(Microsoft.Agents.AI.VectorDataMemory.ChatHistoryMemoryProvider+<>c__DisplayClass20_0).agentId)) AndAlso (x.UserId == value(Microsoft.Agents.AI.VectorDataMemory.ChatHistoryMemoryProvider+<>c__DisplayClass20_0).userId)) AndAlso (x.SessionId == value(Microsoft.Agents.AI.VectorDataMemory.ChatHistoryMemoryProvider+<>c__DisplayClass20_0).sessionId))"; Assert.Equal(ExpectedFilter, options.Filter!.ToString()); }) .Returns(ToAsyncEnumerableAsync(new List>>())); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(searchScope, searchScope), options: providerOptions); var requestMsg = new ChatMessage(ChatRole.User, "requesting relevant history"); var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, new TestAgentSession(), new AIContext { Messages = new List { requestMsg } }); // Act await provider.InvokingAsync(invokingContext, CancellationToken.None); // Assert this._vectorStoreCollectionMock.Verify( c => c.SearchAsync( It.Is(s => s == "requesting relevant history"), 2, It.IsAny>>(), It.IsAny()), Times.Once); } [Theory] [InlineData(false, false, 2)] [InlineData(true, false, 2)] [InlineData(false, true, 2)] [InlineData(true, true, 2)] public async Task InvokingAsync_LogsUserIdBasedOnEnableSensitiveTelemetryDataAsync(bool enableSensitiveTelemetryData, bool requestThrows, int expectedLogInvocations) { // Arrange var options = new ChatHistoryMemoryProviderOptions { SearchTime = ChatHistoryMemoryProviderOptions.SearchBehavior.BeforeAIInvoke, EnableSensitiveTelemetryData = enableSensitiveTelemetryData }; var scope = new ChatHistoryMemoryProviderScope { UserId = "user1" }; if (requestThrows) { this._vectorStoreCollectionMock .Setup(c => c.SearchAsync( It.IsAny(), It.IsAny(), It.IsAny>>(), It.IsAny())) .Throws(new InvalidOperationException("Search failed")); } else { this._vectorStoreCollectionMock .Setup(c => c.SearchAsync( It.IsAny(), It.IsAny(), It.IsAny>>(), It.IsAny())) .Returns(ToAsyncEnumerableAsync(new List>>())); } var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(scope, scope), options: options, loggerFactory: this._loggerFactoryMock.Object); var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, new TestAgentSession(), new AIContext { Messages = new List { new(ChatRole.User, "requesting relevant history") } }); // Act await provider.InvokingAsync(invokingContext, CancellationToken.None); // Assert Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count); foreach (var logInvocation in this._loggerMock.Invocations) { if (logInvocation.Method.Name == nameof(ILogger.IsEnabled)) { continue; } var state = Assert.IsType>>(logInvocation.Arguments[2], exactMatch: false); var userIdValue = state.First(kvp => kvp.Key == "UserId").Value; Assert.Equal(enableSensitiveTelemetryData ? "user1" : "", userIdValue); var inputValue = state.FirstOrDefault(kvp => kvp.Key == "Input").Value; if (inputValue != null) { Assert.Equal(enableSensitiveTelemetryData ? "Who am I?" : "", inputValue); } var messageTextValue = state.FirstOrDefault(kvp => kvp.Key == "MessageText").Value; if (messageTextValue != null) { Assert.Equal(enableSensitiveTelemetryData ? "## Memories\nConsider the following memories when answering user questions:\nName is Caoimhe" : "", messageTextValue); } } } #endregion #region Message Filter Tests [Fact] public async Task InvokingAsync_DefaultFilter_ExcludesNonExternalMessagesFromSearchAsync() { // Arrange var providerOptions = new ChatHistoryMemoryProviderOptions { SearchTime = ChatHistoryMemoryProviderOptions.SearchBehavior.BeforeAIInvoke, }; string? capturedQuery = null; this._vectorStoreCollectionMock .Setup(c => c.SearchAsync( It.IsAny(), It.IsAny(), It.IsAny>>(), It.IsAny())) .Callback>, CancellationToken>((query, _, _, _) => capturedQuery = query) .Returns(ToAsyncEnumerableAsync(new List>>())); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }), options: providerOptions); var requestMessages = new List { new(ChatRole.User, "External message"), new(ChatRole.System, "From history") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "HistorySource") } } }, new(ChatRole.System, "From context provider") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "ContextSource") } } }, }; var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, new TestAgentSession(), new AIContext { Messages = requestMessages }); // Act await provider.InvokingAsync(invokingContext, CancellationToken.None); // Assert - Only External message used for search query Assert.Equal("External message", capturedQuery); } [Fact] public async Task InvokingAsync_CustomSearchInputFilter_OverridesDefaultAsync() { // Arrange var providerOptions = new ChatHistoryMemoryProviderOptions { SearchTime = ChatHistoryMemoryProviderOptions.SearchBehavior.BeforeAIInvoke, SearchInputMessageFilter = messages => messages // No filtering }; string? capturedQuery = null; this._vectorStoreCollectionMock .Setup(c => c.SearchAsync( It.IsAny(), It.IsAny(), It.IsAny>>(), It.IsAny())) .Callback>, CancellationToken>((query, _, _, _) => capturedQuery = query) .Returns(ToAsyncEnumerableAsync(new List>>())); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }), options: providerOptions); var requestMessages = new List { new(ChatRole.User, "External message"), new(ChatRole.System, "From history") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "HistorySource") } } }, }; var invokingContext = new AIContextProvider.InvokingContext(s_mockAgent, new TestAgentSession(), new AIContext { Messages = requestMessages }); // Act await provider.InvokingAsync(invokingContext, CancellationToken.None); // Assert - Both messages should be included in search query (identity filter) Assert.NotNull(capturedQuery); Assert.Contains("External message", capturedQuery); Assert.Contains("From history", capturedQuery); } [Fact] public async Task InvokedAsync_DefaultFilter_ExcludesNonExternalMessagesFromStorageAsync() { // Arrange var stored = new List>(); this._vectorStoreCollectionMock .Setup(c => c.UpsertAsync(It.IsAny>>(), It.IsAny())) .Callback>, CancellationToken>((items, ct) => { if (items != null) { stored.AddRange(items); } }) .Returns(Task.CompletedTask); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" })); var requestMessages = new List { new(ChatRole.User, "External message"), new(ChatRole.System, "From history") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "HistorySource") } } }, new(ChatRole.System, "From context provider") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.AIContextProvider, "ContextSource") } } }, }; var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]); // Act await provider.InvokedAsync(invokedContext, CancellationToken.None); // Assert - Only External message + response stored (ChatHistory and AIContextProvider excluded by default) Assert.Equal(2, stored.Count); Assert.Equal("External message", stored[0]["Content"]); Assert.Equal("Response", stored[1]["Content"]); } [Fact] public async Task InvokedAsync_CustomStorageInputFilter_OverridesDefaultAsync() { // Arrange var stored = new List>(); this._vectorStoreCollectionMock .Setup(c => c.UpsertAsync(It.IsAny>>(), It.IsAny())) .Callback>, CancellationToken>((items, ct) => { if (items != null) { stored.AddRange(items); } }) .Returns(Task.CompletedTask); var provider = new ChatHistoryMemoryProvider( this._vectorStoreMock.Object, TestCollectionName, 1, _ => new ChatHistoryMemoryProvider.State(new ChatHistoryMemoryProviderScope { UserId = "UID" }), options: new ChatHistoryMemoryProviderOptions { StorageInputMessageFilter = messages => messages // No filtering - store everything }); var requestMessages = new List { new(ChatRole.User, "External message"), new(ChatRole.System, "From history") { AdditionalProperties = new() { { AgentRequestMessageSourceAttribution.AdditionalPropertiesKey, new AgentRequestMessageSourceAttribution(AgentRequestMessageSourceType.ChatHistory, "HistorySource") } } }, }; var invokedContext = new AIContextProvider.InvokedContext(s_mockAgent, new TestAgentSession(), requestMessages, [new ChatMessage(ChatRole.Assistant, "Response")]); // Act await provider.InvokedAsync(invokedContext, CancellationToken.None); // Assert - All messages stored (identity filter overrides default) Assert.Equal(3, stored.Count); Assert.Equal("External message", stored[0]["Content"]); Assert.Equal("From history", stored[1]["Content"]); Assert.Equal("Response", stored[2]["Content"]); } #endregion private static async IAsyncEnumerable ToAsyncEnumerableAsync(IEnumerable values) { await Task.Yield(); foreach (var update in values) { yield return update; } } private sealed class TestAgentSession : AgentSession { public TestAgentSession() { } public TestAgentSession(AgentSessionStateBag stateBag) { this.StateBag = stateBag; } } }