diff --git a/src/libraries/Core/Protocols/Microsoft.Agents.Protocols/Connector/RestUserTokenClient.cs b/src/libraries/Core/Protocols/Microsoft.Agents.Protocols/Connector/RestUserTokenClient.cs index d3a151a..880bc7f 100644 --- a/src/libraries/Core/Protocols/Microsoft.Agents.Protocols/Connector/RestUserTokenClient.cs +++ b/src/libraries/Core/Protocols/Microsoft.Agents.Protocols/Connector/RestUserTokenClient.cs @@ -12,6 +12,9 @@ using System.Linq; using Microsoft.Extensions.Logging; using Microsoft.Agents.Protocols.Serializer; +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.Agents.Connector.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b5fc90e7027f67871e773a8fde8938c81dd402ba65b9201d60593e96c492651e889cc13f1415ebb53fac1131ae0bd333c5ee6021672d9718ea31a8aebd0da0072f25d87dba6fc90ffd598ed4da35e44c398c454307e8e33b8426143daec9f596836f97c8f74750e5975c64e2189f45def46b2a2b1247adc3652bf5c308055da9")] namespace Microsoft.Agents.Protocols.Connector { diff --git a/src/tests/Microsoft.Agents.Protocol.Connector.Tests/BotSignInRestClientTests.cs b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/BotSignInRestClientTests.cs new file mode 100644 index 0000000..89052fd --- /dev/null +++ b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/BotSignInRestClientTests.cs @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Azure.Core.Pipeline; +using Azure; +using Microsoft.Agents.Protocols.Primitives; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.Agents.Protocols.Connector.Tests +{ + public class BotSignInRestClientTests + { + private static readonly Uri Endpoint = new("http://localhost"); + private const string State = "state"; + private const string CodeCallenge = "code-challenge"; + private const string EmulatorUrl = "emulator-url"; + private const string FinalRedirect = "final-redirect"; + + [Fact] + public void Constructor_ShouldInstantiateCorrectly() + { + var client = UseClient(); + Assert.NotNull(client); + } + + [Fact] + public void Constructor_ShouldThrowOnNullEndpoint() + { + var pipeline = CreateHttpPipeline(); + Assert.Throws(() => new BotSignInRestClient(pipeline, null)); + } + + [Fact] + public void Constructor_ShouldThrowOnNullHttpPipeline() + { + Assert.Throws(() => new BotSignInRestClient(null, Endpoint)); + } + + + [Fact] + public async Task GetSignInUrlAsync_ShouldThrowOnNullState() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetSignInUrlAsync(null)); + } + + [Fact] + public async Task GetSignInUrlAsync_ShouldThrowWithoutLocalBot() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetSignInUrlAsync(State, CodeCallenge, EmulatorUrl, FinalRedirect)); + } + + [Fact] + public async Task GetSignInResourceAsync_ShouldThrowOnNullState() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetSignInResourceAsync(null, null)); + } + + [Fact] + public async Task GetSignInResourceAsync_ShouldThrowWithoutLocalBot() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetSignInResourceAsync(State, CodeCallenge, EmulatorUrl, FinalRedirect)); + } + + private static HttpPipeline CreateHttpPipeline(int maxRetries = 0) + { + var options = new ConnectorClientOptions(); + options.Retry.MaxRetries = maxRetries; + var pipeline = HttpPipelineBuilder.Build(options, new DefaultHeadersPolicy(options)); + return pipeline; + } + + private static BotSignInRestClient UseClient() + { + return new BotSignInRestClient(CreateHttpPipeline(), Endpoint); + } + } +} diff --git a/src/tests/Microsoft.Agents.Protocol.Connector.Tests/Microsoft.Agents.Connector.Tests.csproj b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/Microsoft.Agents.Connector.Tests.csproj index 423a516..db358b1 100644 --- a/src/tests/Microsoft.Agents.Protocol.Connector.Tests/Microsoft.Agents.Connector.Tests.csproj +++ b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/Microsoft.Agents.Connector.Tests.csproj @@ -22,6 +22,7 @@ + diff --git a/src/tests/Microsoft.Agents.Protocol.Connector.Tests/RestUserTokenClientTests.cs b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/RestUserTokenClientTests.cs new file mode 100644 index 0000000..f001953 --- /dev/null +++ b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/RestUserTokenClientTests.cs @@ -0,0 +1,189 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Agents.Protocols.Primitives; +using Xunit; +using Moq; +using Microsoft.Agents.Authentication; +using System.Collections.Generic; +using System.Threading.Tasks; +using System.Threading; + +namespace Microsoft.Agents.Protocols.Connector.Tests +{ + public class RestUserTokenClientTests + { + private const string AppId = "test-app-id"; + private const string Audience = "audience"; + private const string UserId = "user-id"; + private const string ConnectionName = "connection-name"; + private const string ChannelId = "channel-id"; + private const string MagicCode = "magic-code"; + private const string FinalRedirect = "final-redirect"; + private const string IncludeFilter = "include-filter"; + private static readonly Uri OauthEndpoint = new("https://test.endpoint"); + private static readonly List Scopes = []; + private readonly string[] ResourceUrls = ["https://test.url"]; + private static readonly Mock AccessTokenMock = new(); + private readonly TokenExchangeRequest TokenExchangeRequest = new(); + + [Fact] + public void Constructor_ShouldInstantiateCorrectly() + { + var client = UseClient(); + Assert.NotNull(client); + } + + [Fact] + public void Constructor_ShouldThrowOnNullAppId() + { + Assert.Throws(() => new RestUserTokenClient(null, OauthEndpoint, AccessTokenMock.Object, Audience, Scopes, null)); + } + + [Fact] + public async Task GetUserTokenAsync_ShouldThrowOnDisposed() + { + var client = UseClient(); + client.Dispose(); + await Assert.ThrowsAsync(() => client.GetUserTokenAsync(UserId, ConnectionName, ChannelId, MagicCode, CancellationToken.None)); + } + + [Fact] + public async Task GetUserTokenAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetUserTokenAsync(null, ConnectionName, ChannelId, MagicCode, CancellationToken.None)); + } + + [Fact] + public async Task GetUserTokenAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetUserTokenAsync(UserId, null, ChannelId, MagicCode, CancellationToken.None)); + } + + [Fact] + public async Task GetSignInResourceAsync_ShouldThrowOnDisposed() + { + var client = UseClient(); + client.Dispose(); + await Assert.ThrowsAsync(() => client.GetSignInResourceAsync(ConnectionName, new Activity(), FinalRedirect, CancellationToken.None)); + } + + [Fact] + public async Task GetSignInResourceAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetSignInResourceAsync(null, new Activity(), FinalRedirect, CancellationToken.None)); + } + + [Fact] + public async Task GetSignInResourceAsync_ShouldThrowOnNullActivity() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetSignInResourceAsync(ConnectionName, null, FinalRedirect, CancellationToken.None)); + } + + [Fact] + public async Task SignOutUserAsync_ShouldThrowOnDisposed() + { + var client = UseClient(); + client.Dispose(); + await Assert.ThrowsAsync(() => client.SignOutUserAsync(UserId, ConnectionName, ChannelId, CancellationToken.None)); + } + + [Fact] + public async Task SignOutUserAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.SignOutUserAsync(null, ConnectionName, ChannelId, CancellationToken.None)); + } + + [Fact] + public async Task SignOutUserAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.SignOutUserAsync(UserId, null, ChannelId, CancellationToken.None)); + } + + [Fact] + public async Task GetTokenStatusAsync_ShouldThrowOnDisposed() + { + var client = UseClient(); + client.Dispose(); + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(UserId, ConnectionName, ChannelId, CancellationToken.None)); + } + + [Fact] + public async Task GetTokenStatusAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(null, ChannelId, IncludeFilter, CancellationToken.None)); + } + + [Fact] + public async Task GetTokenStatusAsync_ShouldThrowOnNullChannelId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(UserId, null, IncludeFilter, CancellationToken.None)); + } + + [Fact] + public async Task GetAadTokensAsync_ShouldThrowOnDisposed() + { + var client = UseClient(); + client.Dispose(); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, ConnectionName, ResourceUrls, ChannelId, CancellationToken.None)); + } + + [Fact] + public async Task GetAadTokensAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(null, ConnectionName, ResourceUrls, ChannelId, CancellationToken.None)); + } + + [Fact] + public async Task GetAadTokensAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, null, ResourceUrls, ChannelId, CancellationToken.None)); + } + + [Fact] + public async Task ExchangeTokenAsync_ShouldThrowOnDisposed() + { + var client = UseClient(); + client.Dispose(); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None)); + } + + [Fact] + public async Task ExchangeTokenAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(null, ConnectionName, ChannelId, TokenExchangeRequest, CancellationToken.None)); + } + + [Fact] + public async Task ExchangeTokenAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.ExchangeTokenAsync(UserId, null, ChannelId, TokenExchangeRequest, CancellationToken.None)); + } + + [Fact] + public void Constructor_ShouldDisposeTwiceCorrectly() + { + var client = UseClient(); + client.Dispose(); + client.Dispose(); + } + + private static RestUserTokenClient UseClient() + { + return new RestUserTokenClient(AppId, OauthEndpoint, AccessTokenMock.Object, Audience, Scopes, null); + } + } +} diff --git a/src/tests/Microsoft.Agents.Protocol.Connector.Tests/RetryParamsTests.cs b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/RetryParamsTests.cs new file mode 100644 index 0000000..331b4a9 --- /dev/null +++ b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/RetryParamsTests.cs @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Microsoft.Agents.Protocols.Primitives; +using Xunit; + +namespace Microsoft.Agents.Protocols.Connector.Tests +{ + public class RetryParamTests + { + [Fact] + public void Constructor_ShouldStopRetrying() + { + var retryParams = RetryParams.StopRetrying; + Assert.False(retryParams.ShouldRetry); + } + + [Fact] + public void Constructor_ShouldRetryByDefault() + { + var retryParams = RetryParams.DefaultBackOff(0); + Assert.True(retryParams.ShouldRetry); + Assert.Equal(TimeSpan.FromMilliseconds(50), retryParams.RetryAfter); + } + + [Fact] + public void Constructor_ShouldEnforceOnMaxRetries() + { + var retryParams = RetryParams.DefaultBackOff(10); + Assert.False(retryParams.ShouldRetry); + } + + [Fact] + public void Constructor_ShouldEnforceOnMaxDelay() + { + var retryParams = new RetryParams(TimeSpan.FromSeconds(11), true); + Assert.Equal(TimeSpan.FromSeconds(10), retryParams.RetryAfter); + } + } +} diff --git a/src/tests/Microsoft.Agents.Protocol.Connector.Tests/UserTokenRestClientTests.cs b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/UserTokenRestClientTests.cs new file mode 100644 index 0000000..6a82093 --- /dev/null +++ b/src/tests/Microsoft.Agents.Protocol.Connector.Tests/UserTokenRestClientTests.cs @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Azure; +using Azure.Core.Pipeline; +using Microsoft.Agents.Protocols.Primitives; +using Xunit; + +namespace Microsoft.Agents.Protocols.Connector.Tests +{ + public class UserTokenRestClientTests + { + private static readonly Uri Endpoint = new("http://localhost"); + private const string UserId = "user-id"; + private const string ConnectionName = "connection-name"; + private const string ChannelId = "channel-id"; + private const string Code = "code"; + private const string Include = "include"; + private readonly AadResourceUrls AadResourceUrls = new() { ResourceUrls = ["resource-url"] }; + private readonly TokenExchangeRequest TokenExchangeRequest = new(); + + [Fact] + public void Constructor_ShouldInstantiateCorrectly() + { + var client = UseClient(); + Assert.NotNull(client); + } + + [Fact] + public void Constructor_ShouldThrowOnNullEndpoint() + { + var pipeline = CreateHttpPipeline(); + Assert.Throws(() => new UserTokenRestClient(pipeline, null)); + } + + [Fact] + public void Constructor_ShouldThrowOnNullHttpPipeline() + { + Assert.Throws(() => new UserTokenRestClient(null, Endpoint)); + } + + [Fact] + public async Task GetTokenAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetTokenAsync(null, ConnectionName, ChannelId)); + } + + [Fact] + public async Task GetTokenAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetTokenAsync(UserId, null, ChannelId)); + } + + [Fact] + public async Task GetTokenAsync_ShouldThrowWithoutLocalBot() + { + var client = UseClient(); + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS")) && + Environment.GetEnvironmentVariable("AGENT_OS").Equals("Windows_NT", StringComparison.Ordinal)) + { + // Automated Windows build does not throw an exception. + await client.GetTokenAsync(UserId, ConnectionName, ChannelId, Code); + } + else + { + // MacLinux build and local build exception: + await Assert.ThrowsAsync(() => client.GetTokenAsync(UserId, ConnectionName, ChannelId, Code)); + } + } + + [Fact] + public async Task SignOutAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.SignOutAsync(null, ConnectionName, ChannelId)); + } + + [Fact] + public async Task SignOutAsync_ShouldThrowWithoutLocalBot() + { + var client = UseClient(); + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS")) && + Environment.GetEnvironmentVariable("AGENT_OS").Equals("Windows_NT", StringComparison.Ordinal)) + { + // Automated Windows build exception: + await Assert.ThrowsAsync(() => client.SignOutAsync(UserId, ConnectionName, ChannelId)); + } + else + { + // MacLinux build and local build exception: + await Assert.ThrowsAsync(() => client.SignOutAsync(UserId, ConnectionName, ChannelId)); + } + } + + [Fact] + public async Task GetTokenStatusAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(null)); + } + + [Fact] + public async Task GetTokenStatusAsync_ShouldThrowWithoutLocalBot() + { + var client = UseClient(); + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS")) && + Environment.GetEnvironmentVariable("AGENT_OS").Equals("Windows_NT", StringComparison.Ordinal)) + { + // Automated Windows build exception: + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(UserId, ChannelId, Include)); + } + else + { + // MacLinux build and local build exception: + await Assert.ThrowsAsync(() => client.GetTokenStatusAsync(UserId, ChannelId, Include)); + } + } + + [Fact] + public async Task GetAadTokensAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(null, ConnectionName, AadResourceUrls)); + } + + [Fact] + public async Task GetAadTokensAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, null, AadResourceUrls)); + } + + [Fact] + public async Task GetAadTokensAsync_ShouldThrowWithoutLocalBot() + { + var client = UseClient(); + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS")) && + Environment.GetEnvironmentVariable("AGENT_OS").Equals("Windows_NT", StringComparison.Ordinal)) + { + // Automated Windows build exception: + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, ConnectionName, AadResourceUrls, ChannelId)); + } + else + { + // MacLinux build and local build exception: + await Assert.ThrowsAsync(() => client.GetAadTokensAsync(UserId, ConnectionName, AadResourceUrls, ChannelId)); + } + } + + [Fact] + public async Task ExchangeAsync_ShouldThrowOnNullUserId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(null, ConnectionName, ChannelId, TokenExchangeRequest)); + } + + [Fact] + public async Task ExchangeAsync_ShouldThrowOnNullConnectionName() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, null, ChannelId, TokenExchangeRequest)); + } + + [Fact] + public async Task ExchangeAsync_ShouldThrowOnNullChannelId() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, ConnectionName, null, TokenExchangeRequest)); + } + + [Fact] + public async Task ExchangeAsync_ShouldThrowOnNullExchangeRequest() + { + var client = UseClient(); + await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, null)); + } + + [Fact] + public async Task ExchangeAsync_ShouldThrowWithoutLocalBot() + { + var client = UseClient(); + + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("AGENT_OS")) && + Environment.GetEnvironmentVariable("AGENT_OS").Equals("Windows_NT", StringComparison.Ordinal)) + { + // Automated Windows build exception: + await client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest); + Assert.True(true, "No exception thrown."); + } + else + { + // MacLinux build and local build exception: + await Assert.ThrowsAsync(() => client.ExchangeAsyncAsync(UserId, ConnectionName, ChannelId, TokenExchangeRequest)); + } + } + + private static HttpPipeline CreateHttpPipeline(int maxRetries = 0) + { + var options = new ConnectorClientOptions(); + options.Retry.MaxRetries = maxRetries; + var pipeline = HttpPipelineBuilder.Build(options, new DefaultHeadersPolicy(options)); + return pipeline; + } + + private static UserTokenRestClient UseClient() + { + return new UserTokenRestClient(CreateHttpPipeline(), Endpoint); + } + } +}