// Copyright (c) Microsoft. All rights reserved. using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Agents.AI.Hosting.OpenAI.Conversations; namespace Microsoft.Agents.AI.Hosting.OpenAI.UnitTests; /// /// Unit tests for InMemoryAgentConversationIndex implementation. /// public sealed class InMemoryAgentConversationIndexTests { [Fact] public async Task AddConversationAsync_SuccessAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_test123"; const string ConversationId = "conv_test123"; // Act await index.AddConversationAsync(AgentId, ConversationId); // Assert var response = await index.GetConversationIdsAsync(AgentId); Assert.Single(response.Data); Assert.Contains(ConversationId, response.Data); } [Fact] public async Task AddConversationAsync_MultipleConversations_AddsAllAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_multi"; const string ConversationId1 = "conv_001"; const string ConversationId2 = "conv_002"; const string ConversationId3 = "conv_003"; // Act await index.AddConversationAsync(AgentId, ConversationId1); await index.AddConversationAsync(AgentId, ConversationId2); await index.AddConversationAsync(AgentId, ConversationId3); // Assert var response = await index.GetConversationIdsAsync(AgentId); Assert.Equal(3, response.Data.Count); Assert.Contains(ConversationId1, response.Data); Assert.Contains(ConversationId2, response.Data); Assert.Contains(ConversationId3, response.Data); } [Fact] public async Task AddConversationAsync_NullAgentId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.AddConversationAsync(null!, "conv_test")); } [Fact] public async Task AddConversationAsync_EmptyAgentId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.AddConversationAsync(string.Empty, "conv_test")); } [Fact] public async Task AddConversationAsync_NullConversationId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.AddConversationAsync("agent_test", null!)); } [Fact] public async Task AddConversationAsync_EmptyConversationId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.AddConversationAsync("agent_test", string.Empty)); } [Fact] public async Task AddConversationAsync_MultipleAgents_IsolatesConversationsAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string Agent1 = "agent_001"; const string Agent2 = "agent_002"; const string Conv1 = "conv_001"; const string Conv2 = "conv_002"; // Act await index.AddConversationAsync(Agent1, Conv1); await index.AddConversationAsync(Agent2, Conv2); // Assert var agent1Response = await index.GetConversationIdsAsync(Agent1); var agent2Response = await index.GetConversationIdsAsync(Agent2); Assert.Single(agent1Response.Data); Assert.Contains(Conv1, agent1Response.Data); Assert.DoesNotContain(Conv2, agent1Response.Data); Assert.Single(agent2Response.Data); Assert.Contains(Conv2, agent2Response.Data); Assert.DoesNotContain(Conv1, agent2Response.Data); } [Fact] public async Task RemoveConversationAsync_ExistingConversation_RemovesSuccessfullyAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_remove"; const string ConversationId = "conv_remove123"; await index.AddConversationAsync(AgentId, ConversationId); // Act await index.RemoveConversationAsync(AgentId, ConversationId); // Assert var response = await index.GetConversationIdsAsync(AgentId); Assert.Empty(response.Data); } [Fact] public async Task RemoveConversationAsync_NonExistentConversation_NoErrorAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_noremove"; // Act - Should not throw await index.RemoveConversationAsync(AgentId, "conv_nonexistent"); // Assert var response = await index.GetConversationIdsAsync(AgentId); Assert.Empty(response.Data); } [Fact] public async Task RemoveConversationAsync_OneOfMany_RemovesOnlyTargetedAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_partial"; const string Conv1 = "conv_001"; const string Conv2 = "conv_002"; const string Conv3 = "conv_003"; await index.AddConversationAsync(AgentId, Conv1); await index.AddConversationAsync(AgentId, Conv2); await index.AddConversationAsync(AgentId, Conv3); // Act await index.RemoveConversationAsync(AgentId, Conv2); // Assert var response = await index.GetConversationIdsAsync(AgentId); Assert.Equal(2, response.Data.Count); Assert.Contains(Conv1, response.Data); Assert.DoesNotContain(Conv2, response.Data); Assert.Contains(Conv3, response.Data); } [Fact] public async Task RemoveConversationAsync_NullAgentId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.RemoveConversationAsync(null!, "conv_test")); } [Fact] public async Task RemoveConversationAsync_EmptyAgentId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.RemoveConversationAsync(string.Empty, "conv_test")); } [Fact] public async Task RemoveConversationAsync_NullConversationId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.RemoveConversationAsync("agent_test", null!)); } [Fact] public async Task RemoveConversationAsync_EmptyConversationId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.RemoveConversationAsync("agent_test", string.Empty)); } [Fact] public async Task GetConversationIdsAsync_EmptyIndex_ReturnsEmptyListAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act var response = await index.GetConversationIdsAsync("agent_empty"); // Assert Assert.NotNull(response); Assert.Empty(response.Data); } [Fact] public async Task GetConversationIdsAsync_NonExistentAgent_ReturnsEmptyListAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); await index.AddConversationAsync("agent_other", "conv_001"); // Act var response = await index.GetConversationIdsAsync("agent_nonexistent"); // Assert Assert.NotNull(response); Assert.Empty(response.Data); } [Fact] public async Task GetConversationIdsAsync_NullAgentId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( async () => await index.GetConversationIdsAsync(null!)); } [Fact] public async Task GetConversationIdsAsync_EmptyAgentId_ThrowsArgumentExceptionAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); // Act & Assert await Assert.ThrowsAsync( () => index.GetConversationIdsAsync(string.Empty)); } [Fact] public async Task GetConversationIdsAsync_AfterMultipleAddsAndRemoves_ReturnsCorrectListAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_complex"; await index.AddConversationAsync(AgentId, "conv_001"); await index.AddConversationAsync(AgentId, "conv_002"); await index.AddConversationAsync(AgentId, "conv_003"); await index.RemoveConversationAsync(AgentId, "conv_002"); await index.AddConversationAsync(AgentId, "conv_004"); await index.RemoveConversationAsync(AgentId, "conv_001"); // Act var response = await index.GetConversationIdsAsync(AgentId); // Assert Assert.Equal(2, response.Data.Count); Assert.Contains("conv_003", response.Data); Assert.Contains("conv_004", response.Data); Assert.DoesNotContain("conv_001", response.Data); Assert.DoesNotContain("conv_002", response.Data); } [Fact] public async Task ConcurrentOperations_ThreadSafeAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_concurrent"; const int OperationCount = 100; // Act - Add conversations concurrently var addTasks = new List(); for (int i = 0; i < OperationCount; i++) { int index_local = i; addTasks.Add(Task.Run(async () => await index.AddConversationAsync(AgentId, $"conv_{index_local:D3}"))); } await Task.WhenAll(addTasks); // Assert var response = await index.GetConversationIdsAsync(AgentId); Assert.Equal(OperationCount, response.Data.Count); // Act - Remove half of them concurrently var removeTasks = new List(); for (int i = 0; i < OperationCount / 2; i++) { int index_local = i; removeTasks.Add(Task.Run(async () => await index.RemoveConversationAsync(AgentId, $"conv_{index_local:D3}"))); } await Task.WhenAll(removeTasks); // Assert response = await index.GetConversationIdsAsync(AgentId); Assert.Equal(OperationCount / 2, response.Data.Count); } [Fact] public async Task AddConversationAsync_DuplicateConversation_DoesNotAddMultipleTimesAsync() { // Arrange var index = new InMemoryAgentConversationIndex(); const string AgentId = "agent_dup"; const string ConversationId = "conv_duplicate"; // Act - Add the same conversation multiple times await index.AddConversationAsync(AgentId, ConversationId); await index.AddConversationAsync(AgentId, ConversationId); await index.AddConversationAsync(AgentId, ConversationId); // Assert - HashSet prevents duplicates var response = await index.GetConversationIdsAsync(AgentId); Assert.Single(response.Data); Assert.Contains(ConversationId, response.Data); } }