diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 2ddc685..0000000 --- a/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,12 +0,0 @@ -## Actual Behavior -1. -2. - -## Expected Behavior -1. -2. - -## Versions -- OS platform and version: -- .NET Version: -- NuGet package version or commit ID: \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..238610d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,22 @@ +--- +name: Bug report +about: Report a bug to help us improve +title: '' +labels: '' +assignees: '' + +--- + + +## Actual Behavior +1. +2. + +## Expected Behavior +1. +2. + +## Versions +- OS platform and version: +- .NET Version: +- NuGet package version or commit ID: \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..0ca2c19 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,24 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** + +A clear and concise description of what the problem is. E.g. “I’m always frustrated when [...]” + +**Describe the solution you’d like** + +A clear and concise description of what you want to happen. + +**Describe alternatives you’ve considered** + +A clear and concise description of any alternative solutions or features you’ve considered. + +**Additional context** + +Add any other context or screenshots about the feature request here. diff --git a/src/Microsoft.Azure.ServiceBus/Amqp/AmqpConnectionHelper.cs b/src/Microsoft.Azure.ServiceBus/Amqp/AmqpConnectionHelper.cs index 1cb8ba5..91eeb44 100644 --- a/src/Microsoft.Azure.ServiceBus/Amqp/AmqpConnectionHelper.cs +++ b/src/Microsoft.Azure.ServiceBus/Amqp/AmqpConnectionHelper.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.ServiceBus.Amqp { using System; + using System.Net; using Microsoft.Azure.Amqp; using Microsoft.Azure.Amqp.Sasl; using Microsoft.Azure.Amqp.Transport; @@ -101,7 +102,8 @@ namespace Microsoft.Azure.ServiceBus.Amqp public static TransportSettings CreateWebSocketTransportSettings( string networkHost, string hostName, - int port) + int port, + IWebProxy proxy) { var uriBuilder = new UriBuilder( WebSocketConstants.WebSocketSecureScheme, @@ -112,7 +114,8 @@ namespace Microsoft.Azure.ServiceBus.Amqp { Uri = uriBuilder.Uri, ReceiveBufferSize = AmqpConstants.TransportBufferSize, - SendBufferSize = AmqpConstants.TransportBufferSize + SendBufferSize = AmqpConstants.TransportBufferSize, + Proxy = proxy }; TransportSettings tpSettings = webSocketTransportSettings; diff --git a/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs b/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs index 72613d6..64dcf7a 100644 --- a/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs +++ b/src/Microsoft.Azure.ServiceBus/Core/MessageSender.cs @@ -237,6 +237,11 @@ namespace Microsoft.Azure.ServiceBus.Core this.ThrowIfClosed(); var count = MessageSender.ValidateMessages(messageList); + if (count <= 0) + { + return; + } + MessagingEventSource.Log.MessageSendStart(this.ClientId, count); bool isDiagnosticSourceEnabled = ServiceBusDiagnosticSource.IsEnabled(); diff --git a/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs b/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs index 617611b..ac566fa 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/ManagementClient.cs @@ -18,7 +18,6 @@ namespace Microsoft.Azure.ServiceBus.Management private HttpClient httpClient; private readonly string endpointFQDN; private readonly ITokenProvider tokenProvider; - private readonly TimeSpan operationTimeout; private readonly int port; private readonly string clientId; @@ -48,14 +47,13 @@ namespace Microsoft.Azure.ServiceBus.Management /// Token provider which will generate security tokens for authorization. public ManagementClient(ServiceBusConnectionStringBuilder connectionStringBuilder, ITokenProvider tokenProvider = default) { - this.httpClient = new HttpClient(); + this.httpClient = new HttpClient { Timeout = connectionStringBuilder.OperationTimeout }; this.endpointFQDN = connectionStringBuilder.Endpoint; this.tokenProvider = tokenProvider ?? CreateTokenProvider(connectionStringBuilder); - this.operationTimeout = Constants.DefaultOperationTimeout; this.port = GetPort(connectionStringBuilder.Endpoint); this.clientId = nameof(ManagementClient) + Guid.NewGuid().ToString("N").Substring(0, 6); - MessagingEventSource.Log.ManagementClientCreated(this.clientId, this.operationTimeout.TotalSeconds, this.tokenProvider.ToString()); + MessagingEventSource.Log.ManagementClientCreated(this.clientId, this.httpClient.Timeout.TotalSeconds, this.tokenProvider.ToString()); } public static HttpRequestMessage CloneRequest(HttpRequestMessage req) diff --git a/src/Microsoft.Azure.ServiceBus/Management/NamespaceInfoExtensions.cs b/src/Microsoft.Azure.ServiceBus/Management/NamespaceInfoExtensions.cs index 0b108ba..391be09 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/NamespaceInfoExtensions.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/NamespaceInfoExtensions.cs @@ -57,11 +57,19 @@ namespace Microsoft.Azure.ServiceBus.Management case "Alias": nsInfo.Alias = element.Value; break; + case "MessagingUnits": + int.TryParse(element.Value, out var units); + nsInfo.MessagingUnits = units; + break; case "NamespaceType": if (Enum.TryParse(element.Value, out var nsType)) { nsInfo.NamespaceType = nsType; } + else if (element.Value == "Messaging") // TODO: workaround till next major as it's a breaking change + { + nsInfo.NamespaceType = NamespaceType.ServiceBus; + } else { nsInfo.NamespaceType = NamespaceType.Others; diff --git a/src/Microsoft.Azure.ServiceBus/Management/NamespaceType.cs b/src/Microsoft.Azure.ServiceBus/Management/NamespaceType.cs index 4e61d68..9a0b466 100644 --- a/src/Microsoft.Azure.ServiceBus/Management/NamespaceType.cs +++ b/src/Microsoft.Azure.ServiceBus/Management/NamespaceType.cs @@ -15,7 +15,7 @@ namespace Microsoft.Azure.ServiceBus.Management /// /// Supported only for backward compatibility. - /// Namespace can contain mixture of messaging entities and notification hubs. + /// Namespace can contain mixture of messaging entities and notification hubs. /// Mixed = 2, diff --git a/src/Microsoft.Azure.ServiceBus/Message.cs b/src/Microsoft.Azure.ServiceBus/Message.cs index fb20401..b8698ba 100644 --- a/src/Microsoft.Azure.ServiceBus/Message.cs +++ b/src/Microsoft.Azure.ServiceBus/Message.cs @@ -268,7 +268,7 @@ namespace Microsoft.Azure.ServiceBus /// /// Gets the total size of the message body in bytes. /// - public long Size => Body.Length; + public long Size => this.Body != null ? this.Body.Length : 0; /// /// Gets the "user properties" bag, which can be used for custom message metadata. diff --git a/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj b/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj index 3988d4b..5b31c53 100644 --- a/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj +++ b/src/Microsoft.Azure.ServiceBus/Microsoft.Azure.ServiceBus.csproj @@ -23,14 +23,14 @@ false false true - CS1591;CS1573 + CS1591;CS1573;NU5125 - + - + diff --git a/src/Microsoft.Azure.ServiceBus/ServiceBusConnection.cs b/src/Microsoft.Azure.ServiceBus/ServiceBusConnection.cs index 0cb58ce..766bb35 100644 --- a/src/Microsoft.Azure.ServiceBus/ServiceBusConnection.cs +++ b/src/Microsoft.Azure.ServiceBus/ServiceBusConnection.cs @@ -4,6 +4,7 @@ namespace Microsoft.Azure.ServiceBus { using System; + using System.Net; using System.Threading.Tasks; using Microsoft.Azure.Amqp; using Microsoft.Azure.Amqp.Framing; @@ -37,7 +38,7 @@ namespace Microsoft.Azure.ServiceBus /// Namespace connection string /// It is the responsibility of the user to close the connection after use through public ServiceBusConnection(string namespaceConnectionString) - : this(namespaceConnectionString, Constants.DefaultOperationTimeout, RetryPolicy.Default) + : this(namespaceConnectionString, RetryPolicy.Default) { } @@ -45,11 +46,10 @@ namespace Microsoft.Azure.ServiceBus /// Creates a new connection to service bus. /// /// Namespace connection string. - /// Duration after which individual operations will timeout. /// Retry policy for operations. Defaults to /// It is the responsibility of the user to close the connection after use through - public ServiceBusConnection(string namespaceConnectionString, TimeSpan operationTimeout, RetryPolicy retryPolicy = null) - : this(operationTimeout, retryPolicy) + public ServiceBusConnection(string namespaceConnectionString, RetryPolicy retryPolicy = null) + : this(retryPolicy) { if (string.IsNullOrWhiteSpace(namespaceConnectionString)) { @@ -65,6 +65,33 @@ namespace Microsoft.Azure.ServiceBus this.InitializeConnection(serviceBusConnectionStringBuilder); } + /// + /// Creates a new connection to service bus. + /// + /// Namespace connection string. + /// Duration after which individual operations will timeout. + /// Retry policy for operations. Defaults to + /// It is the responsibility of the user to close the connection after use through + [Obsolete("This constructor is obsolete. Use ServiceBusConnection(string namespaceConnectionString, RetryPolicy retryPolicy) constructor instead, providing operationTimeout in the connection string.")] + public ServiceBusConnection(string namespaceConnectionString, TimeSpan operationTimeout, RetryPolicy retryPolicy = null) + : this(retryPolicy) + { + if (string.IsNullOrWhiteSpace(namespaceConnectionString)) + { + throw Fx.Exception.ArgumentNullOrWhiteSpace(nameof(namespaceConnectionString)); + } + + var serviceBusConnectionStringBuilder = new ServiceBusConnectionStringBuilder(namespaceConnectionString); + if (!string.IsNullOrWhiteSpace(serviceBusConnectionStringBuilder.EntityPath)) + { + throw Fx.Exception.Argument(nameof(namespaceConnectionString), "NamespaceConnectionString should not contain EntityPath."); + } + + this.InitializeConnection(serviceBusConnectionStringBuilder); + // operationTimeout argument explicitly provided by caller should take precedence over OperationTimeout found in the connection string. + this.OperationTimeout = operationTimeout; + } + /// /// Creates a new connection to service bus. /// @@ -72,7 +99,7 @@ namespace Microsoft.Azure.ServiceBus /// Transport type. /// Retry policy for operations. Defaults to public ServiceBusConnection(string endpoint, TransportType transportType, RetryPolicy retryPolicy = null) - : this(Constants.DefaultOperationTimeout, retryPolicy) + : this(retryPolicy) { if (string.IsNullOrWhiteSpace(endpoint)) { @@ -88,9 +115,8 @@ namespace Microsoft.Azure.ServiceBus this.InitializeConnection(serviceBusConnectionStringBuilder); } - internal ServiceBusConnection(TimeSpan operationTimeout, RetryPolicy retryPolicy = null) + internal ServiceBusConnection(RetryPolicy retryPolicy = null) { - this.OperationTimeout = operationTimeout; this.RetryPolicy = retryPolicy ?? RetryPolicy.Default; this.syncLock = new object(); } @@ -193,6 +219,7 @@ namespace Microsoft.Azure.ServiceBus this.TokenProvider = new SharedAccessSignatureTokenProvider(builder.SasKeyName, builder.SasKey); } + this.OperationTimeout = builder.OperationTimeout; this.TransportType = builder.TransportType; this.ConnectionManager = new FaultTolerantAmqpObject(this.CreateConnectionAsync, CloseConnection); this.TransactionController = new FaultTolerantAmqpObject(this.CreateControllerAsync, CloseController); @@ -283,7 +310,8 @@ namespace Microsoft.Azure.ServiceBus return AmqpConnectionHelper.CreateWebSocketTransportSettings( networkHost: networkHost, hostName: hostName, - port: port); + port: port, + proxy: WebRequest.DefaultWebProxy); } return AmqpConnectionHelper.CreateTcpTransportSettings( diff --git a/src/Microsoft.Azure.ServiceBus/ServiceBusConnectionStringBuilder.cs b/src/Microsoft.Azure.ServiceBus/ServiceBusConnectionStringBuilder.cs index 676bfc6..2574efe 100644 --- a/src/Microsoft.Azure.ServiceBus/ServiceBusConnectionStringBuilder.cs +++ b/src/Microsoft.Azure.ServiceBus/ServiceBusConnectionStringBuilder.cs @@ -5,6 +5,7 @@ namespace Microsoft.Azure.ServiceBus { using System; using System.Collections.Generic; + using System.Globalization; using System.Text; using Primitives; @@ -24,6 +25,8 @@ namespace Microsoft.Azure.ServiceBus const string EntityPathConfigName = "EntityPath"; const string TransportTypeConfigName = "TransportType"; + const string OperationTimeoutConfigName = "OperationTimeout"; + string entityPath, sasKeyName, sasKey, sasToken, endpoint; /// @@ -212,6 +215,12 @@ namespace Microsoft.Azure.ServiceBus /// public TransportType TransportType { get; set; } + /// + /// Duration after which individual operations will timeout. + /// + /// Defaults to 1 minute. + public TimeSpan OperationTimeout { get; set; } = Constants.DefaultOperationTimeout; + internal Dictionary ConnectionStringProperties = new Dictionary(StringComparer.CurrentCultureIgnoreCase); /// @@ -243,7 +252,12 @@ namespace Microsoft.Azure.ServiceBus if (this.TransportType != TransportType.Amqp) { - connectionStringBuilder.Append($"{TransportTypeConfigName}{KeyValueSeparator}{this.TransportType}"); + connectionStringBuilder.Append($"{TransportTypeConfigName}{KeyValueSeparator}{this.TransportType}{KeyValuePairDelimiter}"); + } + + if (this.OperationTimeout != Constants.DefaultOperationTimeout) + { + connectionStringBuilder.Append($"{OperationTimeoutConfigName}{KeyValueSeparator}{this.OperationTimeout}{KeyValuePairDelimiter}"); } return connectionStringBuilder.ToString().Trim(';'); @@ -319,6 +333,31 @@ namespace Microsoft.Azure.ServiceBus this.TransportType = transportType; } } + else if (key.Equals(OperationTimeoutConfigName, StringComparison.OrdinalIgnoreCase)) + { + if (int.TryParse(value, NumberStyles.Integer, NumberFormatInfo.InvariantInfo, out var timeoutInSeconds)) + { + this.OperationTimeout = TimeSpan.FromSeconds(timeoutInSeconds); + } + else if (TimeSpan.TryParse(value, NumberFormatInfo.InvariantInfo, out var operationTimeout)) + { + this.OperationTimeout = operationTimeout; + } + else + { + throw Fx.Exception.Argument(nameof(connectionString), $"The {OperationTimeoutConfigName} ({value}) format is invalid. It must be an integer representing a number of seconds."); + } + + if (this.OperationTimeout.TotalMilliseconds <= 0) + { + throw Fx.Exception.Argument(nameof(connectionString), $"The {OperationTimeoutConfigName} ({value}) must be greater than zero."); + } + + if (this.OperationTimeout.TotalHours >= 1) + { + throw Fx.Exception.Argument(nameof(connectionString), $"The {OperationTimeoutConfigName} ({value}) must be smaller than one hour."); + } + } else { ConnectionStringProperties[key] = value; diff --git a/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt b/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt index 19f35d0..1e1bae4 100644 --- a/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt +++ b/test/Microsoft.Azure.ServiceBus.UnitTests/API/ApiApprovals.ApproveAzureServiceBus.approved.txt @@ -309,6 +309,10 @@ namespace Microsoft.Azure.ServiceBus { public ServiceBusConnection(Microsoft.Azure.ServiceBus.ServiceBusConnectionStringBuilder connectionStringBuilder) { } public ServiceBusConnection(string namespaceConnectionString) { } + public ServiceBusConnection(string namespaceConnectionString, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null) { } + [System.ObsoleteAttribute("This constructor is obsolete. Use ServiceBusConnection(string namespaceConnection" + + "String, RetryPolicy retryPolicy) constructor instead, providing operationTimeout" + + " in the connection string.")] public ServiceBusConnection(string namespaceConnectionString, System.TimeSpan operationTimeout, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null) { } public ServiceBusConnection(string endpoint, Microsoft.Azure.ServiceBus.TransportType transportType, Microsoft.Azure.ServiceBus.RetryPolicy retryPolicy = null) { } public System.Uri Endpoint { get; set; } @@ -329,6 +333,7 @@ namespace Microsoft.Azure.ServiceBus public ServiceBusConnectionStringBuilder(string endpoint, string entityPath, string sharedAccessSignature, Microsoft.Azure.ServiceBus.TransportType transportType) { } public string Endpoint { get; set; } public string EntityPath { get; set; } + public System.TimeSpan OperationTimeout { get; set; } public string SasKey { get; set; } public string SasKeyName { get; set; } public string SasToken { get; set; } diff --git a/test/Microsoft.Azure.ServiceBus.UnitTests/Management/ManagementClientTests.cs b/test/Microsoft.Azure.ServiceBus.UnitTests/Management/ManagementClientTests.cs index 1241073..0c57436 100644 --- a/test/Microsoft.Azure.ServiceBus.UnitTests/Management/ManagementClientTests.cs +++ b/test/Microsoft.Azure.ServiceBus.UnitTests/Management/ManagementClientTests.cs @@ -639,7 +639,8 @@ namespace Microsoft.Azure.ServiceBus.UnitTests.Management { var nsInfo = await client.GetNamespaceInfoAsync(); Assert.NotNull(nsInfo); - Assert.Equal(MessagingSku.Standard, nsInfo.MessagingSku); // Most CI systems generally use standard, hence this check just to ensure the API is working. + Assert.Equal(MessagingSku.Standard, nsInfo.MessagingSku); // Most CI systems generally use standard, hence this check just to ensure the API is working. + Assert.Equal(NamespaceType.ServiceBus, nsInfo.NamespaceType); // Common namespace type used for testing is messaging. } public void Dispose() diff --git a/test/Microsoft.Azure.ServiceBus.UnitTests/Microsoft.Azure.ServiceBus.UnitTests.csproj b/test/Microsoft.Azure.ServiceBus.UnitTests/Microsoft.Azure.ServiceBus.UnitTests.csproj index 2bcb93c..d2b6540 100644 --- a/test/Microsoft.Azure.ServiceBus.UnitTests/Microsoft.Azure.ServiceBus.UnitTests.csproj +++ b/test/Microsoft.Azure.ServiceBus.UnitTests/Microsoft.Azure.ServiceBus.UnitTests.csproj @@ -36,14 +36,14 @@ - + - - + + @@ -56,7 +56,7 @@ - + diff --git a/test/Microsoft.Azure.ServiceBus.UnitTests/SenderReceiverTests.cs b/test/Microsoft.Azure.ServiceBus.UnitTests/SenderReceiverTests.cs index 97f90db..4ce85af 100644 --- a/test/Microsoft.Azure.ServiceBus.UnitTests/SenderReceiverTests.cs +++ b/test/Microsoft.Azure.ServiceBus.UnitTests/SenderReceiverTests.cs @@ -376,7 +376,7 @@ namespace Microsoft.Azure.ServiceBus.UnitTests var recivedMessage = await receiver.ReceiveAsync().ConfigureAwait(false); Assert.True(Encoding.UTF8.GetString(recivedMessage.Body) == Encoding.UTF8.GetString(messageBody)); - + var connection = sender.ServiceBusConnection; Assert.Throws(() => new MessageSender(connection, TestConstants.PartitionedQueueName)); } @@ -413,7 +413,7 @@ namespace Microsoft.Azure.ServiceBus.UnitTests messageBody = Encoding.UTF8.GetBytes("Message 2"); message = new Message(messageBody); - await sender.SendAsync(message); + await sender.SendAsync(message); recivedMessage = await receiver.ReceiveAsync().ConfigureAwait(false); Assert.True(Encoding.UTF8.GetString(recivedMessage.Body) == Encoding.UTF8.GetString(messageBody)); @@ -459,5 +459,27 @@ namespace Microsoft.Azure.ServiceBus.UnitTests await receiver.CloseAsync().ConfigureAwait(false); } } + + [Theory] + [InlineData(TestConstants.NonPartitionedQueueName)] + [DisplayTestMethodName] + async Task MessageSenderShouldNotThrowWhenSendingEmptyCollection(string queueName) + { + var sender = new MessageSender(TestUtility.NamespaceConnectionString, queueName); + var receiver = new MessageReceiver(TestUtility.NamespaceConnectionString, queueName, ReceiveMode.ReceiveAndDelete); + + try + { + await sender.SendAsync(new List()); + var message = await receiver.ReceiveAsync(TimeSpan.FromSeconds(3)); + Assert.True(message == null, "Expected not to find any messages, but a message was received."); + } + finally + { + await sender.CloseAsync(); + await receiver.CloseAsync(); + } + } + } } \ No newline at end of file diff --git a/test/Microsoft.Azure.ServiceBus.UnitTests/ServiceBusConnectionStringBuilderTests.cs b/test/Microsoft.Azure.ServiceBus.UnitTests/ServiceBusConnectionStringBuilderTests.cs index a1d3509..4c1c0cd 100644 --- a/test/Microsoft.Azure.ServiceBus.UnitTests/ServiceBusConnectionStringBuilderTests.cs +++ b/test/Microsoft.Azure.ServiceBus.UnitTests/ServiceBusConnectionStringBuilderTests.cs @@ -75,6 +75,13 @@ namespace Microsoft.Azure.ServiceBus.UnitTests csBuilder.EntityPath = "myQ"; Assert.Equal("Endpoint=amqps://contoso.servicebus.windows.net;EntityPath=myQ", csBuilder.ToString()); + + csBuilder.EntityPath = ""; + csBuilder.TransportType = TransportType.AmqpWebSockets; + Assert.Equal("Endpoint=amqps://contoso.servicebus.windows.net;TransportType=AmqpWebSockets", csBuilder.ToString()); + + csBuilder.OperationTimeout = TimeSpan.FromSeconds(42); + Assert.Equal("Endpoint=amqps://contoso.servicebus.windows.net;TransportType=AmqpWebSockets;OperationTimeout=00:00:42", csBuilder.ToString()); } [Fact] @@ -126,6 +133,36 @@ namespace Microsoft.Azure.ServiceBus.UnitTests Assert.Equal(TransportType.Amqp, csBuilder.TransportType); } + [Fact] + void ConnectionStringBuilderShouldParseOperationTimeoutAsInteger() + { + var csBuilder = new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key;OperationTimeout=120"); + Assert.Equal(TimeSpan.FromMinutes(2), csBuilder.OperationTimeout); + } + + [Fact] + void ConnectionStringBuilderShouldParseOperationTimeoutAsTimeSpan() + { + var csBuilder = new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key;OperationTimeout=00:12:34"); + Assert.Equal(TimeSpan.FromMinutes(12).Add(TimeSpan.FromSeconds(34)), csBuilder.OperationTimeout); + } + + [Fact] + void ConnectionStringBuilderOperationTimeoutShouldDefaultToOneMinute() + { + var csBuilder = new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key"); + Assert.Equal(Constants.DefaultOperationTimeout, csBuilder.OperationTimeout); + } + + [Fact] + void ConnectionStringBuilderShouldThrowForInvalidOperationTimeout() + { + var exception = Assert.Throws(() => new ServiceBusConnectionStringBuilder("Endpoint=sb://contoso.servicebus.windows.net;SharedAccessKeyName=keyname;SharedAccessKey=key;OperationTimeout=x")); + Assert.Equal("connectionString", exception.ParamName); + Assert.Contains("OperationTimeout", exception.Message); + Assert.Contains("(x)", exception.Message); + } + [Fact] void ConnectionStringBuilderShouldParseToken() {