// Copyright (c) Microsoft. All rights reserved.
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Moq;
namespace Microsoft.Agents.AI.Hosting.UnitTests;
///
/// Unit tests for .
///
public class ClaimsIdentitySessionIsolationKeyProviderTests
{
private const string TestUserId = "test-user-id";
private const string CustomClaimType = "custom-claim-type";
private const string CustomClaimValue = "custom-claim-value";
private readonly Mock _httpContextAccessorMock;
///
/// Initializes a new instance of the class.
///
public ClaimsIdentitySessionIsolationKeyProviderTests()
{
this._httpContextAccessorMock = new Mock();
}
#region Constructor Tests
///
/// Verify that constructor uses default options when options is null.
///
[Fact]
public void UsesDefaultOptionsWhenNull()
{
// Act & Assert - should not throw
var provider = new ClaimsIdentitySessionIsolationKeyProvider(this._httpContextAccessorMock.Object, options: null);
Assert.NotNull(provider);
}
///
/// Verify that constructor accepts null IHttpContextAccessor.
///
[Fact]
public void Constructor_WithNullHttpContextAccessor_DoesNotThrow()
{
// Act & Assert - should not throw
var provider = new ClaimsIdentitySessionIsolationKeyProvider(httpContextAccessor: null);
Assert.NotNull(provider);
}
///
/// Verify that constructor throws ArgumentException when claimType is null.
///
[Fact]
public void RequiresClaimType_NotNull()
{
// Act & Assert
Assert.Throws("options.ClaimType", () =>
new ClaimsIdentitySessionIsolationKeyProvider(
this._httpContextAccessorMock.Object,
new ClaimsIdentitySessionIsolationKeyProviderOptions { ClaimType = null! }));
}
///
/// Verify that constructor throws ArgumentException when claimType is empty.
///
[Fact]
public void RequiresClaimType_NotEmpty()
{
// Act & Assert
Assert.Throws("options.ClaimType", () =>
new ClaimsIdentitySessionIsolationKeyProvider(
this._httpContextAccessorMock.Object,
new ClaimsIdentitySessionIsolationKeyProviderOptions { ClaimType = string.Empty }));
}
///
/// Verify that constructor throws ArgumentException when claimType is whitespace.
///
[Fact]
public void RequiresClaimType_NotWhitespace()
{
// Act & Assert
Assert.Throws("options.ClaimType", () =>
new ClaimsIdentitySessionIsolationKeyProvider(
this._httpContextAccessorMock.Object,
new ClaimsIdentitySessionIsolationKeyProviderOptions { ClaimType = " " }));
}
#endregion
#region GetSessionIsolationKeyAsync Tests
///
/// Verify that GetSessionIsolationKeyAsync extracts the claim value from the default claim type.
///
[Fact]
public async Task GetSessionIsolationKeyAsyncExtractsDefaultClaimTypeAsync()
{
// Arrange
this.SetupHttpContextWithClaim(ClaimsIdentity.DefaultNameClaimType, TestUserId);
var provider = new ClaimsIdentitySessionIsolationKeyProvider(this._httpContextAccessorMock.Object);
// Act
string? result = await provider.GetSessionIsolationKeyAsync();
// Assert
Assert.Equal(TestUserId, result);
}
///
/// Verify that GetSessionIsolationKeyAsync uses custom claim type when specified.
///
[Fact]
public async Task GetSessionIsolationKeyAsyncUsesCustomClaimTypeAsync()
{
// Arrange
this.SetupHttpContextWithClaim(CustomClaimType, CustomClaimValue);
var provider = new ClaimsIdentitySessionIsolationKeyProvider(
this._httpContextAccessorMock.Object,
new ClaimsIdentitySessionIsolationKeyProviderOptions { ClaimType = CustomClaimType });
// Act
string? result = await provider.GetSessionIsolationKeyAsync();
// Assert
Assert.Equal(CustomClaimValue, result);
}
///
/// Verify that GetSessionIsolationKeyAsync returns null when the specified claim is missing.
///
[Fact]
public async Task GetSessionIsolationKeyAsyncReturnsNullWhenClaimMissingAsync()
{
// Arrange
this.SetupHttpContextWithClaim("other-claim", "value");
var provider = new ClaimsIdentitySessionIsolationKeyProvider(this._httpContextAccessorMock.Object);
// Act
string? result = await provider.GetSessionIsolationKeyAsync();
// Assert
Assert.Null(result);
}
///
/// Verify behavior when HttpContextAccessor returns null HttpContext.
///
[Fact]
public async Task GetSessionIsolationKeyAsyncReturnsNullWhenHttpContextNullAsync()
{
// Arrange
this._httpContextAccessorMock.Setup(x => x.HttpContext).Returns((HttpContext?)null);
var provider = new ClaimsIdentitySessionIsolationKeyProvider(this._httpContextAccessorMock.Object);
// Act
string? result = await provider.GetSessionIsolationKeyAsync();
// Assert
Assert.Null(result);
}
///
/// Verify behavior when HttpContextAccessor itself is null.
///
[Fact]
public async Task GetSessionIsolationKeyAsyncReturnsNullWhenHttpContextAccessorNullAsync()
{
// Arrange
var provider = new ClaimsIdentitySessionIsolationKeyProvider(httpContextAccessor: null);
// Act
string? result = await provider.GetSessionIsolationKeyAsync();
// Assert
Assert.Null(result);
}
///
/// Verify that GetSessionIsolationKeyAsync returns the first matching claim when multiple exist.
///
[Fact]
public async Task GetSessionIsolationKeyAsyncReturnsFirstMatchingClaimAsync()
{
// Arrange
const string FirstValue = "first-value";
const string SecondValue = "second-value";
var claims = new[]
{
new Claim(ClaimsIdentity.DefaultNameClaimType, FirstValue),
new Claim(ClaimsIdentity.DefaultNameClaimType, SecondValue),
};
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
var httpContext = new DefaultHttpContext
{
User = principal
};
this._httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContext);
var provider = new ClaimsIdentitySessionIsolationKeyProvider(this._httpContextAccessorMock.Object);
// Act
string? result = await provider.GetSessionIsolationKeyAsync();
// Assert
Assert.Equal(FirstValue, result);
}
///
/// Verify that GetSessionIsolationKeyAsync handles empty claim values.
///
[Fact]
public async Task GetSessionIsolationKeyAsyncHandlesEmptyClaimValueAsync()
{
// Arrange
this.SetupHttpContextWithClaim(ClaimsIdentity.DefaultNameClaimType, string.Empty);
var provider = new ClaimsIdentitySessionIsolationKeyProvider(this._httpContextAccessorMock.Object);
// Act
string? result = await provider.GetSessionIsolationKeyAsync();
// Assert
Assert.Equal(string.Empty, result);
}
#endregion
#region Helper Methods
private void SetupHttpContextWithClaim(string claimType, string claimValue)
{
var claims = new[] { new Claim(claimType, claimValue) };
var identity = new ClaimsIdentity(claims);
var principal = new ClaimsPrincipal(identity);
var httpContext = new DefaultHttpContext
{
User = principal
};
this._httpContextAccessorMock.Setup(x => x.HttpContext).Returns(httpContext);
}
#endregion
}