From 3fe6e330fed375feb60c6401027fb6cec1a8b2c5 Mon Sep 17 00:00:00 2001 From: Mitchell Sternke Date: Tue, 30 Aug 2016 17:31:34 -0700 Subject: [PATCH 1/2] Added support for most sql connection string properties --- .../Connection/ConnectionService.cs | 115 +++++++++++++++++- .../Connection/Contracts/ConnectionDetails.cs | 113 ++++++++++++++++- .../Contracts/ConnectionDetailsExtensions.cs | 24 +++- .../Connection/ConnectionServiceTests.cs | 50 +++++++- 4 files changed, 298 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index a2dd97db..198813e8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -353,10 +353,123 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection connectionBuilder["Integrated Security"] = false; connectionBuilder["User Id"] = connectionDetails.UserName; connectionBuilder["Password"] = connectionDetails.Password; - if( !String.IsNullOrEmpty(connectionDetails.DatabaseName) ) + + // Check for any optional parameters + if (!String.IsNullOrEmpty(connectionDetails.DatabaseName)) { connectionBuilder["Initial Catalog"] = connectionDetails.DatabaseName; } + if (!String.IsNullOrEmpty(connectionDetails.AuthenticationType)) + { + switch(connectionDetails.AuthenticationType) + { + case "Integrated": + connectionBuilder.IntegratedSecurity = true; + break; + case "SqlLogin": + connectionBuilder.IntegratedSecurity = false; + break; + default: + throw new ArgumentException("Invalid value \"" + connectionDetails.AuthenticationType + "\" for AuthenticationType. Valid values are \"Integrated\" and \"SqlLogin\"."); + } + } + if (connectionDetails.Encrypt.HasValue) + { + connectionBuilder.Encrypt = connectionDetails.Encrypt.Value; + } + if (connectionDetails.TrustServerCertificate.HasValue) + { + connectionBuilder.TrustServerCertificate = connectionDetails.TrustServerCertificate.Value; + } + if (connectionDetails.PersistSecurityInfo.HasValue) + { + connectionBuilder.PersistSecurityInfo = connectionDetails.PersistSecurityInfo.Value; + } + if (connectionDetails.ConnectTimeout.HasValue) + { + connectionBuilder.ConnectTimeout = connectionDetails.ConnectTimeout.Value; + } + if (connectionDetails.ConnectRetryCount.HasValue) + { + connectionBuilder.ConnectRetryCount = connectionDetails.ConnectRetryCount.Value; + } + if (connectionDetails.ConnectRetryInterval.HasValue) + { + connectionBuilder.ConnectRetryInterval = connectionDetails.ConnectRetryInterval.Value; + } + if (!String.IsNullOrEmpty(connectionDetails.ApplicationName)) + { + connectionBuilder.ApplicationName = connectionDetails.ApplicationName; + } + if (!String.IsNullOrEmpty(connectionDetails.WorkstationId)) + { + connectionBuilder.WorkstationID = connectionDetails.WorkstationId; + } + if (!String.IsNullOrEmpty(connectionDetails.ApplicationIntent)) + { + ApplicationIntent intent; + switch (connectionDetails.ApplicationIntent) + { + case "ReadOnly": + intent = ApplicationIntent.ReadOnly; + break; + case "ReadWrite": + intent = ApplicationIntent.ReadWrite; + break; + default: + throw new ArgumentException("Invalid value \"" + connectionDetails.ApplicationIntent + "\" for ApplicationIntent. Valid values are \"ReadWrite\" and \"ReadOnly\"."); + } + connectionBuilder.ApplicationIntent = intent; + } + if (!String.IsNullOrEmpty(connectionDetails.CurrentLanguage)) + { + connectionBuilder.CurrentLanguage = connectionDetails.CurrentLanguage; + } + if (connectionDetails.Pooling.HasValue) + { + connectionBuilder.Pooling = connectionDetails.Pooling.Value; + } + if (connectionDetails.MaxPoolSize.HasValue) + { + connectionBuilder.MaxPoolSize = connectionDetails.MaxPoolSize.Value; + } + if (connectionDetails.MinPoolSize.HasValue) + { + connectionBuilder.MinPoolSize = connectionDetails.MinPoolSize.Value; + } + if (connectionDetails.LoadBalanceTimeout.HasValue) + { + connectionBuilder.LoadBalanceTimeout = connectionDetails.LoadBalanceTimeout.Value; + } + if (connectionDetails.Replication.HasValue) + { + connectionBuilder.Replication = connectionDetails.Replication.Value; + } + if (!String.IsNullOrEmpty(connectionDetails.AttachDbFilename)) + { + connectionBuilder.AttachDBFilename = connectionDetails.AttachDbFilename; + } + if (!String.IsNullOrEmpty(connectionDetails.FailoverPartner)) + { + connectionBuilder.FailoverPartner = connectionDetails.FailoverPartner; + } + if (connectionDetails.MultiSubnetFailover.HasValue) + { + connectionBuilder.MultiSubnetFailover = connectionDetails.MultiSubnetFailover.Value; + } + if (connectionDetails.MultipleActiveResultSets.HasValue) + { + connectionBuilder.MultipleActiveResultSets = connectionDetails.MultipleActiveResultSets.Value; + } + if (connectionDetails.PacketSize.HasValue) + { + connectionBuilder.PacketSize = connectionDetails.PacketSize.Value; + } + if (!String.IsNullOrEmpty(connectionDetails.TypeSystemVersion)) + { + connectionBuilder.TypeSystemVersion = connectionDetails.TypeSystemVersion; + } + return connectionBuilder.ToString(); } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs index 0acac867..ce1c6208 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetails.cs @@ -8,6 +8,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts /// /// Message format for the initial connection request /// + /// + /// If this contract is ever changed, be sure to update ConnectionDetailsExtensions methods. + /// public class ConnectionDetails : ConnectionSummary { /// @@ -16,6 +19,114 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts /// public string Password { get; set; } - // TODO Handle full set of properties + /// + /// Gets or sets the authentication to use. + /// + public string AuthenticationType { get; set; } + + /// + /// Gets or sets a Boolean value that indicates whether SQL Server uses SSL encryption for all data sent between the client and server if the server has a certificate installed. + /// + public bool? Encrypt { get; set; } + + /// + /// Gets or sets a value that indicates whether the channel will be encrypted while bypassing walking the certificate chain to validate trust. + /// + public bool? TrustServerCertificate { get; set; } + + /// + /// Gets or sets a Boolean value that indicates if security-sensitive information, such as the password, is not returned as part of the connection if the connection is open or has ever been in an open state. + /// + public bool? PersistSecurityInfo { get; set; } + + /// + /// Gets or sets the length of time (in seconds) to wait for a connection to the server before terminating the attempt and generating an error. + /// + public int? ConnectTimeout { get; set; } + + /// + /// The number of reconnections attempted after identifying that there was an idle connection failure. + /// + public int? ConnectRetryCount { get; set; } + + /// + /// Amount of time (in seconds) between each reconnection attempt after identifying that there was an idle connection failure. + /// + public int? ConnectRetryInterval { get; set; } + + /// + /// Gets or sets the name of the application associated with the connection string. + /// + public string ApplicationName { get; set; } + + /// + /// Gets or sets the name of the workstation connecting to SQL Server. + /// + public string WorkstationId { get; set; } + + /// + /// Declares the application workload type when connecting to a database in an SQL Server Availability Group. + /// + public string ApplicationIntent { get; set; } + + /// + /// Gets or sets the SQL Server Language record name. + /// + public string CurrentLanguage { get; set; } + + /// + /// Gets or sets a Boolean value that indicates whether the connection will be pooled or explicitly opened every time that the connection is requested. + /// + public bool? Pooling { get; set; } + + /// + /// Gets or sets the maximum number of connections allowed in the connection pool for this specific connection string. + /// + public int? MaxPoolSize { get; set; } + + /// + /// Gets or sets the minimum number of connections allowed in the connection pool for this specific connection string. + /// + public int? MinPoolSize { get; set; } + + /// + /// Gets or sets the minimum time, in seconds, for the connection to live in the connection pool before being destroyed. + /// + public int? LoadBalanceTimeout { get; set; } + + /// + /// Gets or sets a Boolean value that indicates whether replication is supported using the connection. + /// + public bool? Replication { get; set; } + + /// + /// Gets or sets a string that contains the name of the primary data file. This includes the full path name of an attachable database. + /// + public string AttachDbFilename { get; set; } + + /// + /// Gets or sets the name or address of the partner server to connect to if the primary server is down. + /// + public string FailoverPartner { get; set; } + + /// + /// If your application is connecting to an AlwaysOn availability group (AG) on different subnets, setting MultiSubnetFailover=true provides faster detection of and connection to the (currently) active server. + /// + public bool? MultiSubnetFailover { get; set; } + + /// + /// When true, an application can maintain multiple active result sets (MARS). + /// + public bool? MultipleActiveResultSets { get; set; } + + /// + /// Gets or sets the size in bytes of the network packets used to communicate with an instance of SQL Server. + /// + public int? PacketSize { get; set; } + + /// + /// Gets or sets a string value that indicates the type system the application expects. + /// + public string TypeSystemVersion { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs index de278dbc..106fa06e 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectionDetailsExtensions.cs @@ -20,7 +20,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts ServerName = details.ServerName, DatabaseName = details.DatabaseName, UserName = details.UserName, - Password = details.Password + Password = details.Password, + AuthenticationType = details.AuthenticationType, + Encrypt = details.Encrypt, + TrustServerCertificate = details.TrustServerCertificate, + PersistSecurityInfo = details.PersistSecurityInfo, + ConnectTimeout = details.ConnectTimeout, + ConnectRetryCount = details.ConnectRetryCount, + ConnectRetryInterval = details.ConnectRetryInterval, + ApplicationName = details.ApplicationName, + WorkstationId = details.WorkstationId, + ApplicationIntent = details.ApplicationIntent, + CurrentLanguage = details.CurrentLanguage, + Pooling = details.Pooling, + MaxPoolSize = details.MaxPoolSize, + MinPoolSize = details.MinPoolSize, + LoadBalanceTimeout = details.LoadBalanceTimeout, + Replication = details.Replication, + AttachDbFilename = details.AttachDbFilename, + FailoverPartner = details.FailoverPartner, + MultiSubnetFailover = details.MultiSubnetFailover, + MultipleActiveResultSets = details.MultipleActiveResultSets, + PacketSize = details.PacketSize, + TypeSystemVersion = details.TypeSystemVersion }; } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs index b6888b0a..1c3cc8d9 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs @@ -7,10 +7,10 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Reflection; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.Test.Utility; using Moq; @@ -197,6 +197,54 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.NotEqual(String.Empty, connectionResult.Messages); } + /// + /// Verify that optional parameters can be built into a connection string for connecting. + /// + [Theory] + [InlineDataAttribute("AuthenticationType", "Integrated")] + [InlineDataAttribute("AuthenticationType", "SqlLogin")] + [InlineDataAttribute("Encrypt", true)] + [InlineDataAttribute("Encrypt", false)] + [InlineDataAttribute("TrustServerCertificate", true)] + [InlineDataAttribute("TrustServerCertificate", false)] + [InlineDataAttribute("PersistSecurityInfo", true)] + [InlineDataAttribute("PersistSecurityInfo", false)] + [InlineDataAttribute("ConnectTimeout", 15)] + [InlineDataAttribute("ConnectRetryCount", 1)] + [InlineDataAttribute("ConnectRetryInterval", 10)] + [InlineDataAttribute("ApplicationName", "vscode-mssql")] + [InlineDataAttribute("WorkstationId", "mycomputer")] + [InlineDataAttribute("ApplicationIntent", "ReadWrite")] + [InlineDataAttribute("ApplicationIntent", "ReadOnly")] + [InlineDataAttribute("CurrentLanguage", "test")] + [InlineDataAttribute("Pooling", false)] + [InlineDataAttribute("Pooling", true)] + [InlineDataAttribute("MaxPoolSize", 100)] + [InlineDataAttribute("MinPoolSize", 0)] + [InlineDataAttribute("LoadBalanceTimeout", 0)] + [InlineDataAttribute("Replication", true)] + [InlineDataAttribute("Replication", false)] + [InlineDataAttribute("AttachDbFilename", "myfile")] + [InlineDataAttribute("FailoverPartner", "partner")] + [InlineDataAttribute("MultiSubnetFailover", true)] + [InlineDataAttribute("MultiSubnetFailover", false)] + [InlineDataAttribute("MultipleActiveResultSets", false)] + [InlineDataAttribute("MultipleActiveResultSets", true)] + [InlineDataAttribute("PacketSize", 8192)] + [InlineDataAttribute("TypeSystemVersion", "Latest")] + public void ConnectingWithOptionalParametersBuildsConnectionString(string propertyName, object propertyValue) + { + // Create a test connection details object and set the property to a specific value + ConnectionDetails details = TestObjects.GetTestConnectionDetails(); + PropertyInfo info = details.GetType().GetProperty(propertyName); + info.SetValue(details, propertyValue); + + // Test that a connection string can be created without exceptions + string connectionString = ConnectionService.BuildConnectionString(details); + Assert.NotNull(connectionString); + Assert.NotEmpty(connectionString); + } + /// /// Verify that the SQL parser correctly detects errors in text /// From a30ff33187805642e95199315f50c7092ab53c9e Mon Sep 17 00:00:00 2001 From: Mitchell Sternke Date: Wed, 31 Aug 2016 16:04:04 -0700 Subject: [PATCH 2/2] Addressing code review feedback --- .../Connection/ConnectionService.cs | 38 +++--- .../Contracts/ConnectParamsExtensions.cs | 43 ++++-- .../Connection/ConnectionServiceTests.cs | 126 ++++++++++++------ 3 files changed, 142 insertions(+), 65 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 198813e8..2824ee38 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -111,11 +111,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection public ConnectResponse Connect(ConnectParams connectionParams) { // Validate parameters - if(connectionParams == null || !connectionParams.IsValid()) + string paramValidationErrorMessage; + if (connectionParams == null) { return new ConnectResponse() { - Messages = "Error: Invalid connection parameters provided." + Messages = "Error: Connection parameters cannot be null." + }; + } + else if (!connectionParams.IsValid(out paramValidationErrorMessage)) + { + return new ConnectResponse() + { + Messages = paramValidationErrorMessage }; } @@ -168,7 +176,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection public bool Disconnect(DisconnectParams disconnectParams) { // Validate parameters - if (disconnectParams == null || String.IsNullOrEmpty(disconnectParams.OwnerUri)) + if (disconnectParams == null || string.IsNullOrEmpty(disconnectParams.OwnerUri)) { return false; } @@ -203,7 +211,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection { // Verify parameters var owner = listDatabasesParams.OwnerUri; - if (String.IsNullOrEmpty(owner)) + if (string.IsNullOrEmpty(owner)) { throw new ArgumentException("OwnerUri cannot be null or empty"); } @@ -355,11 +363,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection connectionBuilder["Password"] = connectionDetails.Password; // Check for any optional parameters - if (!String.IsNullOrEmpty(connectionDetails.DatabaseName)) + if (!string.IsNullOrEmpty(connectionDetails.DatabaseName)) { connectionBuilder["Initial Catalog"] = connectionDetails.DatabaseName; } - if (!String.IsNullOrEmpty(connectionDetails.AuthenticationType)) + if (!string.IsNullOrEmpty(connectionDetails.AuthenticationType)) { switch(connectionDetails.AuthenticationType) { @@ -370,7 +378,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection connectionBuilder.IntegratedSecurity = false; break; default: - throw new ArgumentException("Invalid value \"" + connectionDetails.AuthenticationType + "\" for AuthenticationType. Valid values are \"Integrated\" and \"SqlLogin\"."); + throw new ArgumentException(string.Format("Invalid value \"{0}\" for AuthenticationType. Valid values are \"Integrated\" and \"SqlLogin\".", connectionDetails.AuthenticationType)); } } if (connectionDetails.Encrypt.HasValue) @@ -397,15 +405,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection { connectionBuilder.ConnectRetryInterval = connectionDetails.ConnectRetryInterval.Value; } - if (!String.IsNullOrEmpty(connectionDetails.ApplicationName)) + if (!string.IsNullOrEmpty(connectionDetails.ApplicationName)) { connectionBuilder.ApplicationName = connectionDetails.ApplicationName; } - if (!String.IsNullOrEmpty(connectionDetails.WorkstationId)) + if (!string.IsNullOrEmpty(connectionDetails.WorkstationId)) { connectionBuilder.WorkstationID = connectionDetails.WorkstationId; } - if (!String.IsNullOrEmpty(connectionDetails.ApplicationIntent)) + if (!string.IsNullOrEmpty(connectionDetails.ApplicationIntent)) { ApplicationIntent intent; switch (connectionDetails.ApplicationIntent) @@ -417,11 +425,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection intent = ApplicationIntent.ReadWrite; break; default: - throw new ArgumentException("Invalid value \"" + connectionDetails.ApplicationIntent + "\" for ApplicationIntent. Valid values are \"ReadWrite\" and \"ReadOnly\"."); + throw new ArgumentException(string.Format("Invalid value \"{0}\" for ApplicationIntent. Valid values are \"ReadWrite\" and \"ReadOnly\".", connectionDetails.ApplicationIntent)); } connectionBuilder.ApplicationIntent = intent; } - if (!String.IsNullOrEmpty(connectionDetails.CurrentLanguage)) + if (!string.IsNullOrEmpty(connectionDetails.CurrentLanguage)) { connectionBuilder.CurrentLanguage = connectionDetails.CurrentLanguage; } @@ -445,11 +453,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection { connectionBuilder.Replication = connectionDetails.Replication.Value; } - if (!String.IsNullOrEmpty(connectionDetails.AttachDbFilename)) + if (!string.IsNullOrEmpty(connectionDetails.AttachDbFilename)) { connectionBuilder.AttachDBFilename = connectionDetails.AttachDbFilename; } - if (!String.IsNullOrEmpty(connectionDetails.FailoverPartner)) + if (!string.IsNullOrEmpty(connectionDetails.FailoverPartner)) { connectionBuilder.FailoverPartner = connectionDetails.FailoverPartner; } @@ -465,7 +473,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection { connectionBuilder.PacketSize = connectionDetails.PacketSize.Value; } - if (!String.IsNullOrEmpty(connectionDetails.TypeSystemVersion)) + if (!string.IsNullOrEmpty(connectionDetails.TypeSystemVersion)) { connectionBuilder.TypeSystemVersion = connectionDetails.TypeSystemVersion; } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectParamsExtensions.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectParamsExtensions.cs index d8596447..9f2c7356 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectParamsExtensions.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/Contracts/ConnectParamsExtensions.cs @@ -15,15 +15,42 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection.Contracts /// /// Check that the fields in ConnectParams are all valid /// - public static bool IsValid(this ConnectParams parameters) + public static bool IsValid(this ConnectParams parameters, out string errorMessage) { - return !( - String.IsNullOrEmpty(parameters.OwnerUri) || - parameters.Connection == null || - String.IsNullOrEmpty(parameters.Connection.Password) || - String.IsNullOrEmpty(parameters.Connection.ServerName) || - String.IsNullOrEmpty(parameters.Connection.UserName) - ); + errorMessage = string.Empty; + if (string.IsNullOrEmpty(parameters.OwnerUri)) + { + errorMessage = "Error: OwnerUri cannot be null or empty."; + } + else if (parameters.Connection == null) + { + errorMessage = "Error: Connection details object cannot be null."; + } + else if (string.IsNullOrEmpty(parameters.Connection.ServerName)) + { + errorMessage = "Error: ServerName cannot be null or empty."; + } + else if (string.IsNullOrEmpty(parameters.Connection.AuthenticationType) || parameters.Connection.AuthenticationType == "SqlLogin") + { + // For SqlLogin, username/password cannot be empty + if (string.IsNullOrEmpty(parameters.Connection.UserName)) + { + errorMessage = "Error: UserName cannot be null or empty when using SqlLogin authentication."; + } + else if( string.IsNullOrEmpty(parameters.Connection.Password)) + { + errorMessage = "Error: Password cannot be null or empty when using SqlLogin authentication."; + } + } + + if (string.IsNullOrEmpty(errorMessage)) + { + return true; + } + else + { + return false; + } } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs index 1c3cc8d9..29367d4c 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Connection/ConnectionServiceTests.cs @@ -152,15 +152,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection /// Verify that when connecting with invalid parameters, an error is thrown. /// [Theory] - [InlineDataAttribute(null, "my-server", "test", "sa", "123456")] - [InlineDataAttribute("file://my/sample/file.sql", null, "test", "sa", "123456")] - [InlineDataAttribute("file://my/sample/file.sql", "my-server", "test", null, "123456")] - [InlineDataAttribute("file://my/sample/file.sql", "my-server", "test", "sa", null)] - [InlineDataAttribute("", "my-server", "test", "sa", "123456")] - [InlineDataAttribute("file://my/sample/file.sql", "", "test", "sa", "123456")] - [InlineDataAttribute("file://my/sample/file.sql", "my-server", "test", "", "123456")] - [InlineDataAttribute("file://my/sample/file.sql", "my-server", "test", "sa", "")] - public void ConnectingWithInvalidParametersYieldsErrorMessage(string ownerUri, string server, string database, string userName, string password) + [InlineData("SqlLogin", null, "my-server", "test", "sa", "123456")] + [InlineData("SqlLogin", "file://my/sample/file.sql", null, "test", "sa", "123456")] + [InlineData("SqlLogin", "file://my/sample/file.sql", "my-server", "test", null, "123456")] + [InlineData("SqlLogin", "file://my/sample/file.sql", "my-server", "test", "sa", null)] + [InlineData("SqlLogin", "", "my-server", "test", "sa", "123456")] + [InlineData("SqlLogin", "file://my/sample/file.sql", "", "test", "sa", "123456")] + [InlineData("SqlLogin", "file://my/sample/file.sql", "my-server", "test", "", "123456")] + [InlineData("SqlLogin", "file://my/sample/file.sql", "my-server", "test", "sa", "")] + [InlineData("Integrated", null, "my-server", "test", "sa", "123456")] + [InlineData("Integrated", "file://my/sample/file.sql", null, "test", "sa", "123456")] + [InlineData("Integrated", "", "my-server", "test", "sa", "123456")] + [InlineData("Integrated", "file://my/sample/file.sql", "", "test", "sa", "123456")] + public void ConnectingWithInvalidParametersYieldsErrorMessage(string authType, string ownerUri, string server, string database, string userName, string password) { // Connect with invalid parameters var connectionResult = @@ -172,7 +176,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection ServerName = server, DatabaseName = database, UserName = userName, - Password = password + Password = password, + AuthenticationType = authType } }); @@ -181,6 +186,40 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection Assert.NotEqual(String.Empty, connectionResult.Messages); } + /// + /// Verify that when using integrated authentication, the username and/or password can be empty. + /// + [Theory] + [InlineData(null, null)] + [InlineData(null, "")] + [InlineData("", null)] + [InlineData("", "")] + [InlineData("sa", null)] + [InlineData("sa", "")] + [InlineData(null, "12345678")] + [InlineData("", "12345678")] + public void ConnectingWithNoUsernameOrPasswordWorksForIntegratedAuth(string userName, string password) + { + // Connect + var connectionResult = + TestObjects.GetTestConnectionService() + .Connect(new ConnectParams() + { + OwnerUri = "file:///my/test/file.sql", + Connection = new ConnectionDetails() { + ServerName = "my-server", + DatabaseName = "test", + UserName = userName, + Password = password, + AuthenticationType = "Integrated" + } + }); + + // check that the connection was successful + Assert.NotEmpty(connectionResult.ConnectionId); + Assert.Null(connectionResult.Messages); + } + /// /// Verify that when connecting with a null parameters object, an error is thrown. /// @@ -201,38 +240,38 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection /// Verify that optional parameters can be built into a connection string for connecting. /// [Theory] - [InlineDataAttribute("AuthenticationType", "Integrated")] - [InlineDataAttribute("AuthenticationType", "SqlLogin")] - [InlineDataAttribute("Encrypt", true)] - [InlineDataAttribute("Encrypt", false)] - [InlineDataAttribute("TrustServerCertificate", true)] - [InlineDataAttribute("TrustServerCertificate", false)] - [InlineDataAttribute("PersistSecurityInfo", true)] - [InlineDataAttribute("PersistSecurityInfo", false)] - [InlineDataAttribute("ConnectTimeout", 15)] - [InlineDataAttribute("ConnectRetryCount", 1)] - [InlineDataAttribute("ConnectRetryInterval", 10)] - [InlineDataAttribute("ApplicationName", "vscode-mssql")] - [InlineDataAttribute("WorkstationId", "mycomputer")] - [InlineDataAttribute("ApplicationIntent", "ReadWrite")] - [InlineDataAttribute("ApplicationIntent", "ReadOnly")] - [InlineDataAttribute("CurrentLanguage", "test")] - [InlineDataAttribute("Pooling", false)] - [InlineDataAttribute("Pooling", true)] - [InlineDataAttribute("MaxPoolSize", 100)] - [InlineDataAttribute("MinPoolSize", 0)] - [InlineDataAttribute("LoadBalanceTimeout", 0)] - [InlineDataAttribute("Replication", true)] - [InlineDataAttribute("Replication", false)] - [InlineDataAttribute("AttachDbFilename", "myfile")] - [InlineDataAttribute("FailoverPartner", "partner")] - [InlineDataAttribute("MultiSubnetFailover", true)] - [InlineDataAttribute("MultiSubnetFailover", false)] - [InlineDataAttribute("MultipleActiveResultSets", false)] - [InlineDataAttribute("MultipleActiveResultSets", true)] - [InlineDataAttribute("PacketSize", 8192)] - [InlineDataAttribute("TypeSystemVersion", "Latest")] - public void ConnectingWithOptionalParametersBuildsConnectionString(string propertyName, object propertyValue) + [InlineData("AuthenticationType", "Integrated", "Integrated Security")] + [InlineData("AuthenticationType", "SqlLogin", "Integrated Security")] + [InlineData("Encrypt", true, "Encrypt")] + [InlineData("Encrypt", false, "Encrypt")] + [InlineData("TrustServerCertificate", true, "TrustServerCertificate")] + [InlineData("TrustServerCertificate", false, "TrustServerCertificate")] + [InlineData("PersistSecurityInfo", true, "Persist Security Info")] + [InlineData("PersistSecurityInfo", false, "Persist Security Info")] + [InlineData("ConnectTimeout", 15, "Connect Timeout")] + [InlineData("ConnectRetryCount", 1, "ConnectRetryCount")] + [InlineData("ConnectRetryInterval", 10, "ConnectRetryInterval")] + [InlineData("ApplicationName", "vscode-mssql", "Application Name")] + [InlineData("WorkstationId", "mycomputer", "Workstation ID")] + [InlineData("ApplicationIntent", "ReadWrite", "ApplicationIntent")] + [InlineData("ApplicationIntent", "ReadOnly", "ApplicationIntent")] + [InlineData("CurrentLanguage", "test", "Current Language")] + [InlineData("Pooling", false, "Pooling")] + [InlineData("Pooling", true, "Pooling")] + [InlineData("MaxPoolSize", 100, "Max Pool Size")] + [InlineData("MinPoolSize", 0, "Min Pool Size")] + [InlineData("LoadBalanceTimeout", 0, "Load Balance Timeout")] + [InlineData("Replication", true, "Replication")] + [InlineData("Replication", false, "Replication")] + [InlineData("AttachDbFilename", "myfile", "AttachDbFilename")] + [InlineData("FailoverPartner", "partner", "Failover Partner")] + [InlineData("MultiSubnetFailover", true, "MultiSubnetFailover")] + [InlineData("MultiSubnetFailover", false, "MultiSubnetFailover")] + [InlineData("MultipleActiveResultSets", false, "MultipleActiveResultSets")] + [InlineData("MultipleActiveResultSets", true, "MultipleActiveResultSets")] + [InlineData("PacketSize", 8192, "Packet Size")] + [InlineData("TypeSystemVersion", "Latest", "Type System Version")] + public void ConnectingWithOptionalParametersBuildsConnectionString(string propertyName, object propertyValue, string connectionStringMarker) { // Create a test connection details object and set the property to a specific value ConnectionDetails details = TestObjects.GetTestConnectionDetails(); @@ -243,6 +282,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Connection string connectionString = ConnectionService.BuildConnectionString(details); Assert.NotNull(connectionString); Assert.NotEmpty(connectionString); + + // Verify that the parameter is in the connection string + Assert.True(connectionString.Contains(connectionStringMarker)); } ///