chore: import upstream snapshot with attribution
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
dotnet-build-and-test / dotnet-test-functions (push) Has been cancelled
dotnet-build-and-test / paths-filter (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Debug, windows-latest, net9.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, ubuntu-latest, net8.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-build (Release, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, ubuntu-latest, net10.0) (push) Has been cancelled
dotnet-build-and-test / dotnet-test (Release, integration, true, windows-latest, net472) (push) Has been cancelled
dotnet-build-and-test / dotnet-foundry-hosted-it (push) Has been cancelled
dotnet-build-and-test / dotnet-build-and-test-check (push) Has been cancelled
dotnet-build-and-test / Integration Test Report (push) Has been cancelled
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.Agents.AI.Valkey\Microsoft.Agents.AI.Valkey.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.Agents.AI.Valkey.UnitTests;
|
||||
|
||||
internal sealed class TestAgentSession : AgentSession
|
||||
{
|
||||
public TestAgentSession()
|
||||
{
|
||||
this.StateBag = new AgentSessionStateBag();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class TestHelpers
|
||||
{
|
||||
internal static readonly AIAgent MockAgent = new Mock<AIAgent>().Object;
|
||||
|
||||
internal static ChatHistoryProvider.InvokingContext CreateChatHistoryInvokingContext(
|
||||
IEnumerable<ChatMessage>? requestMessages = null)
|
||||
{
|
||||
#pragma warning disable MAAI001
|
||||
return new ChatHistoryProvider.InvokingContext(
|
||||
MockAgent,
|
||||
new TestAgentSession(),
|
||||
requestMessages ?? [new ChatMessage(ChatRole.User, "test")]);
|
||||
#pragma warning restore MAAI001
|
||||
}
|
||||
|
||||
internal static ChatHistoryProvider.InvokedContext CreateChatHistoryInvokedContext(
|
||||
IEnumerable<ChatMessage> requestMessages,
|
||||
IEnumerable<ChatMessage> responseMessages)
|
||||
{
|
||||
#pragma warning disable MAAI001
|
||||
return new ChatHistoryProvider.InvokedContext(
|
||||
MockAgent,
|
||||
new TestAgentSession(),
|
||||
requestMessages,
|
||||
responseMessages);
|
||||
#pragma warning restore MAAI001
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Moq;
|
||||
using Valkey.Glide;
|
||||
|
||||
namespace Microsoft.Agents.AI.Valkey.UnitTests;
|
||||
|
||||
/// <summary>
|
||||
/// Unit tests for <see cref="ValkeyChatHistoryProvider"/>.
|
||||
/// </summary>
|
||||
public sealed class ValkeyChatHistoryProviderTests
|
||||
{
|
||||
private static Mock<IConnectionMultiplexer> CreateMockConnection(Mock<IDatabase>? dbMock = null)
|
||||
{
|
||||
var mockConnection = new Mock<IConnectionMultiplexer>();
|
||||
dbMock ??= new Mock<IDatabase>();
|
||||
mockConnection.Setup(c => c.GetDatabase()).Returns(dbMock.Object);
|
||||
return mockConnection;
|
||||
}
|
||||
|
||||
// --- Constructor tests ---
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithConnection_SetsProperties()
|
||||
{
|
||||
// Arrange & Act
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection().Object,
|
||||
static (_) => new ValkeyChatHistoryProvider.State("conv-1"),
|
||||
new ValkeyChatHistoryProviderOptions { KeyPrefix = "test_prefix" });
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(provider);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithConnection_NullConnection_Throws()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new ValkeyChatHistoryProvider(
|
||||
null!,
|
||||
static (_) => new ValkeyChatHistoryProvider.State("conv-1")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Constructor_WithConnection_NullStateInitializer_Throws()
|
||||
{
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentNullException>(() =>
|
||||
new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection().Object,
|
||||
null!));
|
||||
}
|
||||
|
||||
// --- State tests ---
|
||||
|
||||
[Fact]
|
||||
public void State_NullConversationId_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentNullException>(() => new ValkeyChatHistoryProvider.State(null!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void State_EmptyConversationId_Throws()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() => new ValkeyChatHistoryProvider.State(""));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void State_ValidConversationId_SetsProperty()
|
||||
{
|
||||
var state = new ValkeyChatHistoryProvider.State("my-conversation");
|
||||
Assert.Equal("my-conversation", state.ConversationId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void State_JsonConstructor_RoundTrips()
|
||||
{
|
||||
// Arrange
|
||||
var original = new ValkeyChatHistoryProvider.State("test-conv");
|
||||
|
||||
// Act
|
||||
var json = JsonSerializer.Serialize(original);
|
||||
var deserialized = JsonSerializer.Deserialize<ValkeyChatHistoryProvider.State>(json);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(deserialized);
|
||||
Assert.Equal("test-conv", deserialized.ConversationId);
|
||||
}
|
||||
|
||||
// --- StateKeys tests ---
|
||||
|
||||
[Fact]
|
||||
public void StateKeys_ReturnsProviderTypeName()
|
||||
{
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection().Object,
|
||||
_ => new ValkeyChatHistoryProvider.State("conv-1"));
|
||||
|
||||
var keys = provider.StateKeys;
|
||||
Assert.Single(keys);
|
||||
Assert.Equal(nameof(ValkeyChatHistoryProvider), keys[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StateKeys_WithCustomKey_ReturnsCustomKey()
|
||||
{
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection().Object,
|
||||
_ => new ValkeyChatHistoryProvider.State("conv-1"),
|
||||
new ValkeyChatHistoryProviderOptions { StateKey = "custom_key" });
|
||||
|
||||
var keys = provider.StateKeys;
|
||||
Assert.Single(keys);
|
||||
Assert.Equal("custom_key", keys[0]);
|
||||
}
|
||||
|
||||
// --- ProvideChatHistoryAsync tests ---
|
||||
|
||||
[Fact]
|
||||
public async Task ProvideChatHistoryAsync_ReturnsDeserializedMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
var dbMock = new Mock<IDatabase>();
|
||||
var msg1 = new ChatMessage(ChatRole.User, "hello");
|
||||
var msg2 = new ChatMessage(ChatRole.Assistant, "hi there");
|
||||
var values = new ValkeyValue[]
|
||||
{
|
||||
JsonSerializer.Serialize(msg1),
|
||||
JsonSerializer.Serialize(msg2)
|
||||
};
|
||||
dbMock.Setup(d => d.ListRangeAsync(It.IsAny<ValkeyKey>(), It.IsAny<long>(), It.IsAny<long>()))
|
||||
.ReturnsAsync(values);
|
||||
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection(dbMock).Object,
|
||||
_ => new ValkeyChatHistoryProvider.State("conv-1"));
|
||||
|
||||
var context = TestHelpers.CreateChatHistoryInvokingContext();
|
||||
|
||||
// Act — should not throw
|
||||
var result = await provider.InvokingAsync(context);
|
||||
var messages = result.ToList();
|
||||
|
||||
// Assert — only the valid message + request message
|
||||
Assert.True(messages.Count >= 1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProvideChatHistoryAsync_WithMaxMessagesToRetrieve_UsesRangeQueryAsync()
|
||||
{
|
||||
// Arrange
|
||||
var dbMock = new Mock<IDatabase>();
|
||||
dbMock.Setup(d => d.ListRangeAsync(It.IsAny<ValkeyKey>(), It.IsAny<long>(), It.IsAny<long>()))
|
||||
.ReturnsAsync([]);
|
||||
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection(dbMock).Object,
|
||||
_ => new ValkeyChatHistoryProvider.State("conv-1"),
|
||||
new ValkeyChatHistoryProviderOptions { MaxMessagesToRetrieve = 5 });
|
||||
|
||||
var context = TestHelpers.CreateChatHistoryInvokingContext();
|
||||
|
||||
// Act
|
||||
await provider.InvokingAsync(context);
|
||||
|
||||
// Assert — should use -5, -1 range
|
||||
dbMock.Verify(d => d.ListRangeAsync(
|
||||
It.IsAny<ValkeyKey>(), -5, -1), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ProvideChatHistoryAsync_CancellationToken_ThrowsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection().Object,
|
||||
_ => new ValkeyChatHistoryProvider.State("conv-1"));
|
||||
|
||||
var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
|
||||
var context = TestHelpers.CreateChatHistoryInvokingContext();
|
||||
|
||||
// Act & Assert
|
||||
await Assert.ThrowsAsync<OperationCanceledException>(() =>
|
||||
provider.InvokingAsync(context, cts.Token).AsTask());
|
||||
}
|
||||
|
||||
// --- StoreChatHistoryAsync tests ---
|
||||
|
||||
[Fact]
|
||||
public async Task StoreChatHistoryAsync_BatchPushesMessagesAsync()
|
||||
{
|
||||
// Arrange
|
||||
var dbMock = new Mock<IDatabase>();
|
||||
dbMock.Setup(d => d.ListRightPushAsync(It.IsAny<ValkeyKey>(), It.IsAny<ValkeyValue[]>()))
|
||||
.ReturnsAsync(2);
|
||||
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection(dbMock).Object,
|
||||
_ => new ValkeyChatHistoryProvider.State("conv-1"));
|
||||
|
||||
var context = TestHelpers.CreateChatHistoryInvokedContext(
|
||||
[new ChatMessage(ChatRole.User, "hello")],
|
||||
[new ChatMessage(ChatRole.Assistant, "hi")]);
|
||||
|
||||
// Act
|
||||
await provider.InvokedAsync(context);
|
||||
|
||||
// Assert — batch push called once with array
|
||||
dbMock.Verify(d => d.ListRightPushAsync(
|
||||
It.IsAny<ValkeyKey>(), It.IsAny<ValkeyValue[]>()), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StoreChatHistoryAsync_WithMaxMessages_TrimsAsync()
|
||||
{
|
||||
// Arrange
|
||||
var dbMock = new Mock<IDatabase>();
|
||||
dbMock.Setup(d => d.ListRightPushAsync(It.IsAny<ValkeyKey>(), It.IsAny<ValkeyValue[]>()))
|
||||
.ReturnsAsync(1);
|
||||
dbMock.Setup(d => d.ListTrimAsync(It.IsAny<ValkeyKey>(), It.IsAny<long>(), It.IsAny<long>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var provider = new ValkeyChatHistoryProvider(
|
||||
CreateMockConnection(dbMock).Object,
|
||||
_ => new ValkeyChatHistoryProvider.State("conv-1"),
|
||||
new ValkeyChatHistoryProviderOptions { MaxMessages = 10 });
|
||||
|
||||
var context = TestHelpers.CreateChatHistoryInvokedContext(
|
||||
[new ChatMessage(ChatRole.User, "hello")],
|
||||
[new ChatMessage(ChatRole.Assistant, "hi")]);
|
||||
|
||||
// Act
|
||||
await provider.InvokedAsync(context);
|
||||
|
||||
// Assert — trim called unconditionally when MaxMessages is set
|
||||
dbMock.Verify(d => d.ListTrimAsync(
|
||||
It.IsAny<ValkeyKey>(), -10, -1), Times.Once);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user