// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Threading;
using System.Threading.Tasks;
using Moq;
namespace Microsoft.Agents.AI.Hosting.UnitTests;
///
/// Unit tests for the class.
///
public class DelegatingAgentSessionStoreTests
{
private readonly Mock _innerStoreMock;
private readonly Mock _agentMock;
private readonly TestDelegatingAgentSessionStore _delegatingStore;
private readonly AgentSession _testSession;
///
/// Initializes a new instance of the class.
///
public DelegatingAgentSessionStoreTests()
{
this._innerStoreMock = new Mock();
this._agentMock = new Mock();
this._testSession = new TestAgentSession();
// Setup inner store mock
this._innerStoreMock
.Setup(x => x.GetSessionAsync(It.IsAny(), It.IsAny(), It.IsAny()))
.ReturnsAsync(this._testSession);
this._innerStoreMock
.Setup(x => x.SaveSessionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(ValueTask.CompletedTask);
this._delegatingStore = new TestDelegatingAgentSessionStore(this._innerStoreMock.Object);
}
#region Constructor Tests
///
/// Verify that constructor throws ArgumentNullException when innerStore is null.
///
[Fact]
public void RequiresInnerStore() =>
// Act & Assert
Assert.Throws("innerStore", () => new TestDelegatingAgentSessionStore(null!));
///
/// Verify that constructor sets the inner store correctly.
///
[Fact]
public void Constructor_WithValidInnerStore_SetsInnerStore()
{
// Act
var delegatingStore = new TestDelegatingAgentSessionStore(this._innerStoreMock.Object);
// Assert
Assert.Same(this._innerStoreMock.Object, delegatingStore.InnerStore);
}
#endregion
#region Method Delegation Tests
///
/// Verify that GetSessionAsync delegates to inner store with correct parameters.
///
[Fact]
public async Task GetSessionAsyncDelegatesToInnerStoreAsync()
{
// Arrange
const string ExpectedConversationId = "test-conversation-id";
var expectedCancellationToken = new CancellationToken();
this._innerStoreMock
.Setup(x => x.GetSessionAsync(
It.Is(a => a == this._agentMock.Object),
It.Is(c => c == ExpectedConversationId),
It.Is(ct => ct == expectedCancellationToken)))
.ReturnsAsync(this._testSession);
// Act
var session = await this._delegatingStore.GetSessionAsync(
this._agentMock.Object,
ExpectedConversationId,
expectedCancellationToken);
// Assert
Assert.Same(this._testSession, session);
this._innerStoreMock.Verify(
x => x.GetSessionAsync(
this._agentMock.Object,
ExpectedConversationId,
expectedCancellationToken),
Times.Once);
}
///
/// Verify that SaveSessionAsync delegates to inner store with correct parameters.
///
[Fact]
public async Task SaveSessionAsyncDelegatesToInnerStoreAsync()
{
// Arrange
const string ExpectedConversationId = "test-conversation-id";
var expectedCancellationToken = new CancellationToken();
var expectedSession = new TestAgentSession();
this._innerStoreMock
.Setup(x => x.SaveSessionAsync(
It.Is(a => a == this._agentMock.Object),
It.Is(c => c == ExpectedConversationId),
It.Is(s => s == expectedSession),
It.Is(ct => ct == expectedCancellationToken)))
.Returns(ValueTask.CompletedTask);
// Act
await this._delegatingStore.SaveSessionAsync(
this._agentMock.Object,
ExpectedConversationId,
expectedSession,
expectedCancellationToken);
// Assert
this._innerStoreMock.Verify(
x => x.SaveSessionAsync(
this._agentMock.Object,
ExpectedConversationId,
expectedSession,
expectedCancellationToken),
Times.Once);
}
///
/// Verify that GetSessionAsync awaits the inner store's result before returning.
///
[Fact]
public async Task GetSessionAsyncAwaitsInnerStoreResultAsync()
{
// Arrange
const string ExpectedConversationId = "test-conversation-id";
var taskCompletionSource = new TaskCompletionSource();
var innerStoreMock = new Mock();
innerStoreMock
.Setup(x => x.GetSessionAsync(It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new ValueTask(taskCompletionSource.Task));
var delegatingStore = new TestDelegatingAgentSessionStore(innerStoreMock.Object);
// Act
var resultTask = delegatingStore.GetSessionAsync(this._agentMock.Object, ExpectedConversationId);
// Assert
Assert.False(resultTask.IsCompleted);
taskCompletionSource.SetResult(this._testSession);
Assert.True(resultTask.IsCompleted);
Assert.Same(this._testSession, await resultTask);
}
///
/// Verify that SaveSessionAsync awaits the inner store's completion before returning.
///
[Fact]
public async Task SaveSessionAsyncAwaitsInnerStoreCompletionAsync()
{
// Arrange
const string ExpectedConversationId = "test-conversation-id";
var expectedSession = new TestAgentSession();
var taskCompletionSource = new TaskCompletionSource();
var innerStoreMock = new Mock();
innerStoreMock
.Setup(x => x.SaveSessionAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()))
.Returns(new ValueTask(taskCompletionSource.Task));
var delegatingStore = new TestDelegatingAgentSessionStore(innerStoreMock.Object);
// Act
var resultTask = delegatingStore.SaveSessionAsync(this._agentMock.Object, ExpectedConversationId, expectedSession);
// Assert
Assert.False(resultTask.IsCompleted);
taskCompletionSource.SetResult();
Assert.True(resultTask.IsCompleted);
await resultTask;
}
#endregion
#region GetService Tests
///
/// Verify that GetService returns itself when requesting the exact type.
///
[Fact]
public void GetServiceReturnsItselfForExactType()
{
// Act
var result = this._delegatingStore.GetService(typeof(TestDelegatingAgentSessionStore));
// Assert
Assert.Same(this._delegatingStore, result);
}
///
/// Verify that GetService returns itself when requesting a base type.
///
[Fact]
public void GetServiceReturnsItselfForBaseType()
{
// Act
var result = this._delegatingStore.GetService(typeof(DelegatingAgentSessionStore));
// Assert
Assert.Same(this._delegatingStore, result);
}
///
/// Verify that GetService returns itself when requesting AgentSessionStore.
///
[Fact]
public void GetServiceReturnsItselfForAgentSessionStoreType()
{
// Act
var result = this._delegatingStore.GetService(typeof(AgentSessionStore));
// Assert
Assert.Same(this._delegatingStore, result);
}
///
/// Verify that GetService chains to inner store when type is not satisfied by outer store.
///
[Fact]
public void GetServiceChainsToInnerStore()
{
// Arrange
var innerStore = new ConcreteAgentSessionStore();
var delegatingStore = new TestDelegatingAgentSessionStore(innerStore);
// Act
var result = delegatingStore.GetService(typeof(ConcreteAgentSessionStore));
// Assert
Assert.Same(innerStore, result);
}
///
/// Verify that GetService chains through multiple delegation layers.
///
[Fact]
public void GetServiceChainsThoughMultipleDelegationLayers()
{
// Arrange - create a three-layer chain: outer -> middle -> inner
var innerStore = new ConcreteAgentSessionStore();
var middleStore = new AnotherDelegatingAgentSessionStore(innerStore);
var outerStore = new TestDelegatingAgentSessionStore(middleStore);
// Act - request the innermost store type
var result = outerStore.GetService(typeof(ConcreteAgentSessionStore));
// Assert
Assert.Same(innerStore, result);
}
///
/// Verify that GetService can find a store in the middle of the delegation chain.
///
[Fact]
public void GetServiceFindsMiddleStoreInChain()
{
// Arrange - create a three-layer chain: outer -> middle -> inner
var innerStore = new ConcreteAgentSessionStore();
var middleStore = new AnotherDelegatingAgentSessionStore(innerStore);
var outerStore = new TestDelegatingAgentSessionStore(middleStore);
// Act - request the middle store type
var result = outerStore.GetService(typeof(AnotherDelegatingAgentSessionStore));
// Assert
Assert.Same(middleStore, result);
}
///
/// Verify that GetService returns null when the requested type is not found in the chain.
///
[Fact]
public void GetServiceReturnsNullWhenTypeNotFound()
{
// Arrange
var innerStore = new ConcreteAgentSessionStore();
var delegatingStore = new TestDelegatingAgentSessionStore(innerStore);
// Act
var result = delegatingStore.GetService(typeof(string));
// Assert
Assert.Null(result);
}
///
/// Verify that GetService returns null when a service key is provided but not matched.
///
[Fact]
public void GetServiceReturnsNullWhenServiceKeyProvided()
{
// Act
var result = this._delegatingStore.GetService(typeof(TestDelegatingAgentSessionStore), "some-key");
// Assert
Assert.Null(result);
}
///
/// Verify that GetService throws ArgumentNullException when serviceType is null.
///
[Fact]
public void GetServiceThrowsWhenServiceTypeIsNull() =>
Assert.Throws("serviceType", () => this._delegatingStore.GetService(null!));
///
/// Verify that GetService generic method works correctly.
///
[Fact]
public void GetServiceGenericReturnsItself()
{
// Act
var result = this._delegatingStore.GetService();
// Assert
Assert.Same(this._delegatingStore, result);
}
///
/// Verify that GetService generic method chains to inner store.
///
[Fact]
public void GetServiceGenericChainsToInnerStore()
{
// Arrange
var innerStore = new ConcreteAgentSessionStore();
var delegatingStore = new TestDelegatingAgentSessionStore(innerStore);
// Act
var result = delegatingStore.GetService();
// Assert
Assert.Same(innerStore, result);
}
///
/// Verify that GetService generic method returns null when type not found.
///
[Fact]
public void GetServiceGenericReturnsNullWhenTypeNotFound()
{
// Act
var result = this._delegatingStore.GetService();
// Assert
Assert.Null(result);
}
#endregion
#region Test Implementation
///
/// Test implementation of DelegatingAgentSessionStore for testing purposes.
///
private sealed class TestDelegatingAgentSessionStore(AgentSessionStore innerStore) : DelegatingAgentSessionStore(innerStore)
{
public new AgentSessionStore InnerStore => base.InnerStore;
}
///
/// Another delegating store implementation for testing multi-layer chains.
///
private sealed class AnotherDelegatingAgentSessionStore(AgentSessionStore innerStore) : DelegatingAgentSessionStore(innerStore);
///
/// Concrete (non-delegating) session store for testing GetService chaining.
///
private sealed class ConcreteAgentSessionStore : AgentSessionStore
{
public override ValueTask GetSessionAsync(AIAgent agent, string conversationId, CancellationToken cancellationToken = default)
=> new(new TestAgentSession());
public override ValueTask SaveSessionAsync(AIAgent agent, string conversationId, AgentSession session, CancellationToken cancellationToken = default)
=> ValueTask.CompletedTask;
}
private sealed class TestAgentSession : AgentSession;
#endregion
}