mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
33f84f9ed2
* Improve fidelity of OpenAI Responses server and add Conversations * Merge * nit * Undo prior change * Undo prior change * Review feedback * Review feedback * Fix test * Use simpler JsonDocument approach for polymorphic deserialization * More review feedback * dotnet format
360 lines
12 KiB
C#
360 lines
12 KiB
C#
// 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;
|
|
|
|
/// <summary>
|
|
/// Unit tests for InMemoryAgentConversationIndex implementation.
|
|
/// </summary>
|
|
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<ArgumentNullException>(
|
|
() => index.AddConversationAsync(null!, "conv_test"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddConversationAsync_EmptyAgentId_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryAgentConversationIndex();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(
|
|
() => index.AddConversationAsync(string.Empty, "conv_test"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddConversationAsync_NullConversationId_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryAgentConversationIndex();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
() => index.AddConversationAsync("agent_test", null!));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task AddConversationAsync_EmptyConversationId_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryAgentConversationIndex();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(
|
|
() => 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<ArgumentNullException>(
|
|
() => index.RemoveConversationAsync(null!, "conv_test"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RemoveConversationAsync_EmptyAgentId_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryAgentConversationIndex();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(
|
|
() => index.RemoveConversationAsync(string.Empty, "conv_test"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RemoveConversationAsync_NullConversationId_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryAgentConversationIndex();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentNullException>(
|
|
() => index.RemoveConversationAsync("agent_test", null!));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RemoveConversationAsync_EmptyConversationId_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryAgentConversationIndex();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(
|
|
() => 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<ArgumentNullException>(
|
|
async () => await index.GetConversationIdsAsync(null!));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetConversationIdsAsync_EmptyAgentId_ThrowsArgumentExceptionAsync()
|
|
{
|
|
// Arrange
|
|
var index = new InMemoryAgentConversationIndex();
|
|
|
|
// Act & Assert
|
|
await Assert.ThrowsAsync<ArgumentException>(
|
|
() => 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<Task>();
|
|
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<Task>();
|
|
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);
|
|
}
|
|
}
|