-
Notifications
You must be signed in to change notification settings - Fork 8
Add runtime unit tests #127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds comprehensive unit tests for runtime utility classes including Utility, AgenticAuthenticationService, and TenantContextHelper. The tests focus on configuration retrieval, environment detection, token handling, and tenant/worker context extraction from various HTTP sources.
Key Changes
- Added unit tests for
Utilityclass methods covering configuration scopes, environment detection, and JWT token parsing - Added unit tests for
AgenticAuthenticationServicecovering authentication token retrieval - Updated
TenantContextHelperTestswith copyright headers and consolidated test cases using[Theory]attributes - Updated project namespace from
Microsoft.Agents.A365.Runtime.Common.TeststoMicrosoft.Agents.A365.Runtime.Tests - Added required JWT token handling dependencies (
System.IdentityModel.Tokens.JwtandMicrosoft.IdentityModel.Tokens)
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/src/Tests/Runtime.Tests/UtilityTests.cs | CRITICAL: File in wrong directory - Should be removed; tests belong in src/Tests/Runtime.Tests/ |
| src/src/Tests/Runtime.Tests/AgenticAuthenticationServiceTests.cs | CRITICAL: File in wrong directory - Should be removed; tests belong in src/Tests/Runtime.Tests/ |
| src/Tests/Runtime.Tests/UtilityTests.cs | Comprehensive unit tests for Utility class; has potential issues with empty string handling expectations |
| src/Tests/Runtime.Tests/AgenticAuthenticationServiceTests.cs | Tests for utility methods but misnamed; tests Utility class, not AgenticAuthenticationService |
| src/Tests/Runtime.Tests/TenantContextHelperTests.cs | Added copyright header, updated namespace, and consolidated tests using Theory attributes |
| src/Tests/Runtime.Tests/Microsoft.Agents.A365.Runtime.Tests.csproj | Updated project assembly and namespace from Common.Tests to Runtime.Tests |
| src/Directory.Packages.props | Added JWT token handling dependencies with appropriate versions (8.8.0) |
| /// Unit tests for AgenticAuthenticationService class. | ||
| /// Tests the authentication token retrieval and utility methods. | ||
| /// </summary> | ||
| public class AgenticAuthenticationServiceTests |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The class name AgenticAuthenticationServiceTests suggests this class tests AgenticAuthenticationService, but the tests are actually testing Utility class methods (Utility.GetMcpPlatformAuthenticationScope and Utility.GetCurrentEnvironment).
Consider renaming this class to better reflect what it's testing, such as UtilityConfigurationTests or moving these tests into the existing UtilityTests class to avoid confusion.
| /// Unit tests for AgenticAuthenticationService class. | |
| /// Tests the authentication token retrieval and utility methods. | |
| /// </summary> | |
| public class AgenticAuthenticationServiceTests | |
| /// Unit tests for configuration-related methods in the Utility class. | |
| /// Tests Utility.GetMcpPlatformAuthenticationScope and Utility.GetCurrentEnvironment. | |
| /// </summary> | |
| public class UtilityConfigurationTests |
| [InlineData("", "")] // Empty string is returned as-is (not replaced with default) | ||
| [InlineData(" ", " ")] // Whitespace is returned as-is (not replaced with default) |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects empty string "" and whitespace " " to be returned as-is, but this contradicts the default behavior pattern where null values return the default scope "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default" (as shown in line 21).
If the actual implementation treats empty/whitespace strings the same as null (returning the default), then these test cases will fail. If empty strings should indeed return as-is, this creates an inconsistent API where null returns a default but "" returns empty string.
Please verify the actual implementation behavior and ensure these test expectations are correct. Consider whether empty/whitespace strings should also trigger default value logic for consistency.
| [InlineData("", "")] // Empty string is returned as-is (not replaced with default) | |
| [InlineData(" ", " ")] // Whitespace is returned as-is (not replaced with default) | |
| [InlineData("", "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default")] // Empty string is treated as null and replaced with default | |
| [InlineData(" ", "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default")] // Whitespace is treated as null and replaced with default |
| public void GetMcpPlatformAuthenticationScope_CalledMultipleTimes_AccessesConfigurationCorrectly( | ||
| string testScope, | ||
| int callCount) | ||
| { | ||
| // Arrange | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns(testScope); | ||
|
|
||
| // Act | ||
| for (int i = 0; i < callCount; i++) | ||
| { | ||
| Utility.GetMcpPlatformAuthenticationScope(mockConfiguration.Object); | ||
| } | ||
|
|
||
| // Assert | ||
| mockConfiguration.Verify(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"], Times.Exactly(callCount)); | ||
| } |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test at line 99 (GetMcpPlatformAuthenticationScope_CalledMultipleTimes_AccessesConfigurationCorrectly) doesn't verify the actual return value of the method being tested. It only verifies that the configuration was accessed the correct number of times.
While verifying interaction count can be useful, this test should also assert that the correct scope value is returned on each call to ensure the method behaves correctly, not just that it accesses configuration. Consider adding assertions like:
var result = Utility.GetMcpPlatformAuthenticationScope(mockConfiguration.Object);
Assert.Equal(testScope, result);inside the loop or after it.
| [InlineData("", "")] // Empty string is returned as-is (not null) | ||
| [InlineData(" ", " ")] // Whitespace is returned as-is (not null) |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects empty strings "" and whitespace " " to be returned as-is (based on the comment), but this contradicts the default behavior pattern. Line 29 shows that when config is null (via "SKIP"), the default scope "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default" is returned.
If the implementation treats empty/whitespace strings the same as null (returning the default), these test expectations are incorrect. If empty strings truly return as-is while null returns a default, this creates an inconsistent API.
Please verify the actual implementation behavior and ensure the test expectations and comments accurately reflect it.
| [InlineData("", "")] // Empty string is returned as-is (not null) | |
| [InlineData(" ", " ")] // Whitespace is returned as-is (not null) | |
| [InlineData("", "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default")] // Empty string is treated as null and returns default scope | |
| [InlineData(" ", "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default")] // Whitespace is treated as null and returns default scope |
| [InlineData("", "", "")] // Empty strings are returned as-is (not replaced with default) | ||
| [InlineData(" ", " ", " ")] // Whitespace is returned as-is (not replaced with default) |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test expects empty strings "" and whitespace " " to return as-is (based on the comment), but this contradicts the default behavior. Line 58 shows that when both config values are null (via "SKIP"), "Development" is returned as the default.
If the implementation treats empty/whitespace strings the same as null (returning "Development"), these test expectations are incorrect and the tests will fail. If empty strings truly return as-is while null returns a default, this creates an inconsistent API.
Please verify the actual implementation behavior and ensure the test expectations and comments accurately reflect it.
| [InlineData("", "", "")] // Empty strings are returned as-is (not replaced with default) | |
| [InlineData(" ", " ", " ")] // Whitespace is returned as-is (not replaced with default) | |
| [InlineData("", "", "Development")] // Empty strings are treated as null and default to "Development" | |
| [InlineData(" ", " ", "Development")] // Whitespace is treated as null and default to "Development" |
| public void GetAppIdFromToken_WithInvalidTokenFormats_ThrowsException(string invalidToken) | ||
| { | ||
| // Act & Assert - Expects SecurityTokenMalformedException or ArgumentException | ||
| Assert.ThrowsAny<Exception>(() => Utility.GetAppIdFromToken(invalidToken)); | ||
| } |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test uses Assert.ThrowsAny<Exception>() which is too broad and will catch any exception type. This makes the test less precise and could pass even if an unexpected exception type is thrown.
Based on the comment mentioning "SecurityTokenMalformedException or ArgumentException", consider using a more specific assertion:
var exception = Assert.ThrowsAny<Exception>(() => Utility.GetAppIdFromToken(invalidToken));
Assert.True(exception is SecurityTokenMalformedException || exception is ArgumentException,
$"Expected SecurityTokenMalformedException or ArgumentException, but got {exception.GetType().Name}");Or test each exception type separately if the behavior is deterministic for each invalid token format.
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Microsoft.Agents.A365.Runtime.Utils; | ||
| using Microsoft.Agents.Builder; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Moq; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.IdentityModel.Tokens.Jwt; | ||
| using System.Linq; | ||
| using System.Security.Claims; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Agents.A365.Runtime.Tests | ||
| { | ||
| /// <summary> | ||
| /// Unit tests for Utility class. | ||
| /// Tests configuration retrieval, environment detection, and token handling utilities. | ||
| /// </summary> | ||
| public class UtilityTests | ||
| { | ||
| #region GetMcpPlatformAuthenticationScope Tests | ||
|
|
||
| [Fact] | ||
| public void GetMcpPlatformAuthenticationScope_WithConfigurationValue_ReturnsConfigurationValue() | ||
| { | ||
| // Arrange | ||
| const string expectedScope = "custom-scope-from-config"; | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns(expectedScope); | ||
|
|
||
| // Act | ||
| var result = Utility.GetMcpPlatformAuthenticationScope(mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedScope, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetMcpPlatformAuthenticationScope_WithNullConfigurationValue_ReturnsDefaultScope() | ||
| { | ||
| // Arrange | ||
| const string expectedDefaultScope = "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"; | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns((string?)null); | ||
|
|
||
| // Act | ||
| var result = Utility.GetMcpPlatformAuthenticationScope(mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedDefaultScope, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetMcpPlatformAuthenticationScope_WithEmptyConfigurationValue_ReturnsDefaultScope() | ||
| { | ||
| // Arrange | ||
| const string expectedDefaultScope = "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"; | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns(string.Empty); | ||
|
|
||
| // Act | ||
| var result = Utility.GetMcpPlatformAuthenticationScope(mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedDefaultScope, result); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region GetCurrentEnvironment Tests | ||
|
|
||
| [Fact] | ||
| public void GetCurrentEnvironment_WithAspNetCoreEnvironment_ReturnsAspNetCoreValue() | ||
| { | ||
| // Arrange | ||
| const string expectedEnvironment = "Production"; | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["ASPNETCORE_ENVIRONMENT"]) | ||
| .Returns(expectedEnvironment); | ||
| mockConfiguration.Setup(c => c["DOTNET_ENVIRONMENT"]) | ||
| .Returns("SomeOtherValue"); | ||
|
|
||
| // Act | ||
| var result = Utility.GetCurrentEnvironment(mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedEnvironment, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetCurrentEnvironment_WithDotNetEnvironmentOnly_ReturnsDotNetValue() | ||
| { | ||
| // Arrange | ||
| const string expectedEnvironment = "Staging"; | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["ASPNETCORE_ENVIRONMENT"]) | ||
| .Returns((string?)null); | ||
| mockConfiguration.Setup(c => c["DOTNET_ENVIRONMENT"]) | ||
| .Returns(expectedEnvironment); | ||
|
|
||
| // Act | ||
| var result = Utility.GetCurrentEnvironment(mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedEnvironment, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetCurrentEnvironment_WithNoEnvironmentVariables_ReturnsDefaultDevelopment() | ||
| { | ||
| // Arrange | ||
| const string expectedDefaultEnvironment = "Development"; | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["ASPNETCORE_ENVIRONMENT"]) | ||
| .Returns((string?)null); | ||
| mockConfiguration.Setup(c => c["DOTNET_ENVIRONMENT"]) | ||
| .Returns((string?)null); | ||
|
|
||
| // Act | ||
| var result = Utility.GetCurrentEnvironment(mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedDefaultEnvironment, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetCurrentEnvironment_WithEmptyEnvironmentVariables_ReturnsDefaultDevelopment() | ||
| { | ||
| // Arrange | ||
| const string expectedDefaultEnvironment = "Development"; | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
| mockConfiguration.Setup(c => c["ASPNETCORE_ENVIRONMENT"]) | ||
| .Returns(string.Empty); | ||
| mockConfiguration.Setup(c => c["DOTNET_ENVIRONMENT"]) | ||
| .Returns(string.Empty); | ||
|
|
||
| // Act | ||
| var result = Utility.GetCurrentEnvironment(mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedDefaultEnvironment, result); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region GetAppIdFromToken Tests | ||
|
|
||
| [Fact] | ||
| public void GetAppIdFromToken_WithValidTokenContainingAppId_ReturnsAppId() | ||
| { | ||
| // Arrange | ||
| const string expectedAppId = "12345678-1234-1234-1234-123456789abc"; | ||
| var token = CreateJwtToken(new Dictionary<string, object> | ||
| { | ||
| { "appid", expectedAppId }, | ||
| { "aud", "test-audience" } | ||
| }); | ||
|
|
||
| // Act | ||
| var result = Utility.GetAppIdFromToken(token); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedAppId, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetAppIdFromToken_WithValidTokenContainingAzp_ReturnsAzp() | ||
| { | ||
| // Arrange | ||
| const string expectedAppId = "87654321-4321-4321-4321-cba987654321"; | ||
| var token = CreateJwtToken(new Dictionary<string, object> | ||
| { | ||
| { "azp", expectedAppId }, | ||
| { "aud", "test-audience" } | ||
| }); | ||
|
|
||
| // Act | ||
| var result = Utility.GetAppIdFromToken(token); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedAppId, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetAppIdFromToken_WithBothAppIdAndAzp_PrioritizesAppId() | ||
| { | ||
| // Arrange | ||
| const string appIdValue = "appid-value"; | ||
| const string azpValue = "azp-value"; | ||
| var token = CreateJwtToken(new Dictionary<string, object> | ||
| { | ||
| { "appid", appIdValue }, | ||
| { "azp", azpValue }, | ||
| { "aud", "test-audience" } | ||
| }); | ||
|
|
||
| // Act | ||
| var result = Utility.GetAppIdFromToken(token); | ||
|
|
||
| // Assert | ||
| Assert.Equal(appIdValue, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetAppIdFromToken_WithNoAppIdOrAzp_ReturnsEmptyString() | ||
| { | ||
| // Arrange | ||
| var token = CreateJwtToken(new Dictionary<string, object> | ||
| { | ||
| { "aud", "test-audience" }, | ||
| { "sub", "test-subject" } | ||
| }); | ||
|
|
||
| // Act | ||
| var result = Utility.GetAppIdFromToken(token); | ||
|
|
||
| // Assert | ||
| Assert.Equal(string.Empty, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetAppIdFromToken_WithNullToken_ReturnsEmptyGuid() | ||
| { | ||
| // Act | ||
| var result = Utility.GetAppIdFromToken(null); | ||
|
|
||
| // Assert | ||
| Assert.Equal(Guid.Empty.ToString(), result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetAppIdFromToken_WithEmptyToken_ReturnsEmptyGuid() | ||
| { | ||
| // Act | ||
| var result = Utility.GetAppIdFromToken(string.Empty); | ||
|
|
||
| // Assert | ||
| Assert.Equal(Guid.Empty.ToString(), result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void GetAppIdFromToken_WithWhitespaceToken_ReturnsEmptyGuid() | ||
| { | ||
| // Act | ||
| var result = Utility.GetAppIdFromToken(" "); | ||
|
|
||
| // Assert | ||
| Assert.Equal(Guid.Empty.ToString(), result); | ||
| } | ||
|
|
||
| [Theory] | ||
| [InlineData("invalid-token")] | ||
| [InlineData("not.a.jwt")] | ||
| [InlineData("header.payload")] | ||
| public void GetAppIdFromToken_WithInvalidToken_ThrowsException(string invalidToken) | ||
| { | ||
| // Act & Assert | ||
| Assert.Throws<ArgumentException>(() => Utility.GetAppIdFromToken(invalidToken)); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region ResolveAgentIdentity Tests | ||
|
|
||
| [Fact] | ||
| public void ResolveAgentIdentity_WithAgenticRequest_ReturnsAgenticInstanceId() | ||
| { | ||
| // Arrange | ||
| const string expectedAgenticInstanceId = "agentic-instance-123"; | ||
| const string authToken = "dummy-token"; | ||
|
|
||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockActivity = new Mock<IActivity>(); | ||
|
|
||
| mockTurnContext.Setup(tc => tc.Activity).Returns(mockActivity.Object); | ||
| mockActivity.Setup(a => a.IsAgenticRequest()).Returns(true); | ||
| mockActivity.Setup(a => a.GetAgenticInstanceId()).Returns(expectedAgenticInstanceId); | ||
|
|
||
| // Act | ||
| var result = Utility.ResolveAgentIdentity(mockTurnContext.Object, authToken); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedAgenticInstanceId, result); | ||
| mockActivity.Verify(a => a.IsAgenticRequest(), Times.Once); | ||
| mockActivity.Verify(a => a.GetAgenticInstanceId(), Times.Once); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveAgentIdentity_WithNonAgenticRequest_ReturnsAppIdFromToken() | ||
| { | ||
| // Arrange | ||
| const string expectedAppId = "token-app-id-456"; | ||
| var authToken = CreateJwtToken(new Dictionary<string, object> | ||
| { | ||
| { "appid", expectedAppId } | ||
| }); | ||
|
|
||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockActivity = new Mock<IActivity>(); | ||
|
|
||
| mockTurnContext.Setup(tc => tc.Activity).Returns(mockActivity.Object); | ||
| mockActivity.Setup(a => a.IsAgenticRequest()).Returns(false); | ||
|
|
||
| // Act | ||
| var result = Utility.ResolveAgentIdentity(mockTurnContext.Object, authToken); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedAppId, result); | ||
| mockActivity.Verify(a => a.IsAgenticRequest(), Times.Once); | ||
| mockActivity.Verify(a => a.GetAgenticInstanceId(), Times.Never); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveAgentIdentity_WithNonAgenticRequestAndInvalidToken_ReturnsEmptyString() | ||
| { | ||
| // Arrange | ||
| const string authToken = "invalid-token"; | ||
|
|
||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockActivity = new Mock<IActivity>(); | ||
|
|
||
| mockTurnContext.Setup(tc => tc.Activity).Returns(mockActivity.Object); | ||
| mockActivity.Setup(a => a.IsAgenticRequest()).Returns(false); | ||
|
|
||
| // Act & Assert | ||
| Assert.Throws<ArgumentException>(() => Utility.ResolveAgentIdentity(mockTurnContext.Object, authToken)); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void ResolveAgentIdentity_WithNonAgenticRequestAndNullToken_ReturnsEmptyGuid() | ||
| { | ||
| // Arrange | ||
| string? authToken = null; | ||
|
|
||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockActivity = new Mock<IActivity>(); | ||
|
|
||
| mockTurnContext.Setup(tc => tc.Activity).Returns(mockActivity.Object); | ||
| mockActivity.Setup(a => a.IsAgenticRequest()).Returns(false); | ||
|
|
||
| // Act | ||
| var result = Utility.ResolveAgentIdentity(mockTurnContext.Object, authToken); | ||
|
|
||
| // Assert | ||
| Assert.Equal(Guid.Empty.ToString(), result); | ||
| } | ||
|
|
||
| #endregion | ||
|
|
||
| #region Helper Methods | ||
|
|
||
| /// <summary> | ||
| /// Creates a JWT token with the specified claims for testing purposes. | ||
| /// </summary> | ||
| /// <param name="claims">Dictionary of claims to include in the token.</param> | ||
| /// <returns>A JWT token string.</returns> | ||
| private static string CreateJwtToken(Dictionary<string, object> claims) | ||
| { | ||
| var handler = new JwtSecurityTokenHandler(); | ||
| var claimsList = claims.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()!)).ToList(); | ||
|
|
||
| var tokenDescriptor = new Microsoft.IdentityModel.Tokens.SecurityTokenDescriptor | ||
| { | ||
| Subject = new ClaimsIdentity(claimsList), | ||
| Expires = DateTime.UtcNow.AddHours(1), | ||
| SigningCredentials = new Microsoft.IdentityModel.Tokens.SigningCredentials( | ||
| new Microsoft.IdentityModel.Tokens.SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes("test-key-that-is-long-enough-for-hmac-sha256")), | ||
| Microsoft.IdentityModel.Tokens.SecurityAlgorithms.HmacSha256Signature) | ||
| }; | ||
|
|
||
| var token = handler.CreateToken(tokenDescriptor); | ||
| return handler.WriteToken(token); | ||
| } | ||
|
|
||
| #endregion | ||
| } | ||
| } No newline at end of file |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This entire test file appears to be in an incorrect directory (src/src/Tests/Runtime.Tests/). The correct location should be src/Tests/Runtime.Tests/ where the project file exists. Additionally, this file appears to be a duplicate or alternative version of the file in src/Tests/Runtime.Tests/UtilityTests.cs (179 lines), creating confusion about which is the authoritative version.
Please remove this file from src/src/Tests/Runtime.Tests/ and consolidate all tests into src/Tests/Runtime.Tests/UtilityTests.cs.
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| using Microsoft.Agents.A365.Runtime.Authentication; | ||
| using Microsoft.Agents.A365.Runtime.Utils; | ||
| using Microsoft.Agents.Builder; | ||
| using Microsoft.Agents.Builder.App.UserAuth; | ||
| using Microsoft.Extensions.Configuration; | ||
| using Moq; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Threading.Tasks; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Agents.A365.Runtime.Tests | ||
| { | ||
| /// <summary> | ||
| /// Unit tests for AgenticAuthenticationService class. | ||
| /// Tests the authentication token retrieval functionality for agentic users. | ||
| /// </summary> | ||
| public class AgenticAuthenticationServiceTests | ||
| { | ||
| [Fact] | ||
| public async Task GetAgenticUserTokenAsync_WithValidParameters_ReturnsToken() | ||
| { | ||
| // Arrange | ||
| const string expectedToken = "test-token-123"; | ||
| const string authHandlerName = "test-handler"; | ||
| const string testScope = "test-scope"; | ||
|
|
||
| var mockUserAuthorization = new Mock<UserAuthorization>(); | ||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
|
|
||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns(testScope); | ||
|
|
||
| mockUserAuthorization | ||
| .Setup(ua => ua.ExchangeTurnTokenAsync( | ||
| It.IsAny<ITurnContext>(), | ||
| It.Is<string>(s => s == authHandlerName), | ||
| It.Is<List<string>>(scopes => scopes.Count == 1 && scopes[0] == testScope))) | ||
| .ReturnsAsync(expectedToken); | ||
|
|
||
| // Act | ||
| var result = await AgenticAuthenticationService.GetAgenticUserTokenAsync( | ||
| mockUserAuthorization.Object, | ||
| authHandlerName, | ||
| mockTurnContext.Object, | ||
| mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedToken, result); | ||
| mockUserAuthorization.Verify(ua => ua.ExchangeTurnTokenAsync( | ||
| mockTurnContext.Object, | ||
| authHandlerName, | ||
| It.Is<List<string>>(scopes => scopes.Count == 1 && scopes[0] == testScope)), | ||
| Times.Once); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task GetAgenticUserTokenAsync_WithNullConfiguration_UsesDefaultScope() | ||
| { | ||
| // Arrange | ||
| const string expectedToken = "test-token-456"; | ||
| const string authHandlerName = "test-handler"; | ||
| const string defaultScope = "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1/.default"; | ||
|
|
||
| var mockUserAuthorization = new Mock<UserAuthorization>(); | ||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
|
|
||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns((string?)null); | ||
|
|
||
| mockUserAuthorization | ||
| .Setup(ua => ua.ExchangeTurnTokenAsync( | ||
| It.IsAny<ITurnContext>(), | ||
| It.Is<string>(s => s == authHandlerName), | ||
| It.Is<List<string>>(scopes => scopes.Count == 1 && scopes[0] == defaultScope))) | ||
| .ReturnsAsync(expectedToken); | ||
|
|
||
| // Act | ||
| var result = await AgenticAuthenticationService.GetAgenticUserTokenAsync( | ||
| mockUserAuthorization.Object, | ||
| authHandlerName, | ||
| mockTurnContext.Object, | ||
| mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedToken, result); | ||
| mockUserAuthorization.Verify(ua => ua.ExchangeTurnTokenAsync( | ||
| mockTurnContext.Object, | ||
| authHandlerName, | ||
| It.Is<List<string>>(scopes => scopes.Count == 1 && scopes[0] == defaultScope)), | ||
| Times.Once); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task GetAgenticUserTokenAsync_WithEmptyAuthHandlerName_PassesEmptyString() | ||
| { | ||
| // Arrange | ||
| const string expectedToken = "test-token-789"; | ||
| const string authHandlerName = ""; | ||
| const string testScope = "test-scope"; | ||
|
|
||
| var mockUserAuthorization = new Mock<UserAuthorization>(); | ||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
|
|
||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns(testScope); | ||
|
|
||
| mockUserAuthorization | ||
| .Setup(ua => ua.ExchangeTurnTokenAsync( | ||
| It.IsAny<ITurnContext>(), | ||
| It.Is<string>(s => s == authHandlerName), | ||
| It.Is<List<string>>(scopes => scopes.Count == 1 && scopes[0] == testScope))) | ||
| .ReturnsAsync(expectedToken); | ||
|
|
||
| // Act | ||
| var result = await AgenticAuthenticationService.GetAgenticUserTokenAsync( | ||
| mockUserAuthorization.Object, | ||
| authHandlerName, | ||
| mockTurnContext.Object, | ||
| mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedToken, result); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task GetAgenticUserTokenAsync_WhenUserAuthorizationThrows_PropagatesException() | ||
| { | ||
| // Arrange | ||
| const string authHandlerName = "test-handler"; | ||
| var expectedException = new InvalidOperationException("Test exception"); | ||
|
|
||
| var mockUserAuthorization = new Mock<UserAuthorization>(); | ||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
|
|
||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns("test-scope"); | ||
|
|
||
| mockUserAuthorization | ||
| .Setup(ua => ua.ExchangeTurnTokenAsync( | ||
| It.IsAny<ITurnContext>(), | ||
| It.IsAny<string>(), | ||
| It.IsAny<List<string>>())) | ||
| .ThrowsAsync(expectedException); | ||
|
|
||
| // Act & Assert | ||
| var actualException = await Assert.ThrowsAsync<InvalidOperationException>( | ||
| () => AgenticAuthenticationService.GetAgenticUserTokenAsync( | ||
| mockUserAuthorization.Object, | ||
| authHandlerName, | ||
| mockTurnContext.Object, | ||
| mockConfiguration.Object)); | ||
|
|
||
| Assert.Same(expectedException, actualException); | ||
| } | ||
|
|
||
| [Fact] | ||
| public async Task GetAgenticUserTokenAsync_CallsUtilityGetMcpPlatformAuthenticationScope() | ||
| { | ||
| // Arrange | ||
| const string expectedToken = "test-token-utility"; | ||
| const string authHandlerName = "test-handler"; | ||
| const string testScope = "custom-scope-from-utility"; | ||
|
|
||
| var mockUserAuthorization = new Mock<UserAuthorization>(); | ||
| var mockTurnContext = new Mock<ITurnContext>(); | ||
| var mockConfiguration = new Mock<IConfiguration>(); | ||
|
|
||
| mockConfiguration.Setup(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"]) | ||
| .Returns(testScope); | ||
|
|
||
| mockUserAuthorization | ||
| .Setup(ua => ua.ExchangeTurnTokenAsync( | ||
| It.IsAny<ITurnContext>(), | ||
| It.IsAny<string>(), | ||
| It.Is<List<string>>(scopes => scopes.Count == 1 && scopes[0] == testScope))) | ||
| .ReturnsAsync(expectedToken); | ||
|
|
||
| // Act | ||
| var result = await AgenticAuthenticationService.GetAgenticUserTokenAsync( | ||
| mockUserAuthorization.Object, | ||
| authHandlerName, | ||
| mockTurnContext.Object, | ||
| mockConfiguration.Object); | ||
|
|
||
| // Assert | ||
| Assert.Equal(expectedToken, result); | ||
|
|
||
| // Verify that the scope passed matches what Utility.GetMcpPlatformAuthenticationScope would return | ||
| mockConfiguration.Verify(c => c["MCP_PLATFORM_AUTHENTICATION_SCOPE"], Times.Once); | ||
| } | ||
| } | ||
| } No newline at end of file |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test file is located in the wrong directory (src/src/Tests/Runtime.Tests/). The correct location should be src/Tests/Runtime.Tests/ where the project file exists. Additionally, there appears to be a duplicate or alternative version of this file in src/Tests/Runtime.Tests/AgenticAuthenticationServiceTests.cs (118 lines).
Please remove this file from src/src/Tests/Runtime.Tests/ and consolidate all tests into src/Tests/Runtime.Tests/AgenticAuthenticationServiceTests.cs.
Add runtime unit tests