mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
* Disable failing test while investigating * Made connection errors more user-friendly (#57) * Bug/negativeOneRowsAffected strings file fix (#61) * Adding changes to sr.strings files * Fixing bug by changing valid filename check to fail on whitespace (#55) Fixing a bug from the unit tests on OSX/Unix where attempting to create a file with a name that's all whitespace succeeds when it should fail. This was passing in Windows because File.Open throws an ArgumentException when an all whitespace name is provided. In Unix systems, File.Open does not throw, causing the test to fail. Solution is to check for whitespace in the sanity check. * Format Cell Values (#62) * WIP for ability to localize cell values * Changing how DateTimeOffsets are stored, getting unit tests going * Reworking BufferFileStreamWriter to use dictionary approach * Plumbing the DbCellValue type the rest of the way through * Removing unused components to simplify contract * Cleanup and making sure byte[] appears in parity with SSMS * CR comments, small tweaks for optimizing LINQ * Feature/batch line info (#56) * inital pipe of line numbers and getting text from workspace services * tests compile * Fixed bug regarding tests using connections on mac * updated tests * fixed workspace service and fixed tests * integrated feedback * Remove deleted file with whitespace name * Feature/autocomp options (#63) * Enable IntelliSense settings * Fix up some bugs in the IntelliSense settings. * Code cleans for PR * Fix a couple exceptions that are breaks query execute and intellisense. * Add useLowerCase flag and settings tests * Remove task.wait from test to avoid break on build machine. (#67) This is to fix test breaks so I'll merge now. Please let me know if there are comments on this commit. * Do not use ReliableCommand in the query execution service (#66) * Do not use ReliableCommand in the query execution service. * Fixing the logic to remove InfoMessage handlers from ReliableSqlConnection * Adding test to query UDT * Bump SMO to 140.1.6 to pick up perf fixes (#69) * Enable Quick Info hover tooltips (#65) Pushing to include in tomorrow's partner release build. Please send me any feedback and I'll address in the next Intellisense PR. * Added grouping between system/user dbs when listing (#70) * Feature/timestamp messages (#68) * added support for timestamps * fixed tests * Moved message class to own file; added 'z' to end of date strings * added default time constructor * removed unnecessary z * added time string format info in comment * changed from utc time to using local time * Feature/save selection (#64) * Save selection * Add tests * Change filename in test * Code cleanup * Refactor handler * Code cleanup * Modify tests to have query selection * Change variable declaration * Bump SMO to 14.0.7 to pick Batchparser updates (#72) ...bumping versions. No review needed. * Lingering File Handles (#71) Fixing a bug where in various situations, the files used for temporary storage of query results would be leftover. In particular, the following changes were made: * When the dispose query request is submitted, the corresponding query is now disposed in addition from being removed from the list of active queries * When a query is cancelled, it is disposed after it is cancelled * If a query already exists for a given ownerURI, the existing query is disposed before creating a new query * All queries are disposed when the query execution service is disposed (ie, at shutdown of the service) A unit test to verify the action of the dispose method for a ResultSet was added. * Ensuring queries are disposed Adding logic to dispose any queries when: * URI that already has a query executes another query * A request to dispose a query is submitted * A request to cancel a query is submitted * Small tweaks for cleanup of query execution service * Add IntelliSense binding queue (#73) * Initial code for binding queue * Fix-up some of the timeout wait code * Add some initial test code * Add missing test file * Update the binding queue tests * Add more test coverage and refactor a bit. Disable reliabile connection until we can fix it..it's holding an open data reader connection. * A few more test updates * Initial integrate queue with language service. * Hook up the connected binding queue into al binding calls. * Cleanup comments and remove dead code * More missing comments * Fix build break. Reenable ReliabileConnection. * Revert all changes to SqlConnectionFactory * Resolve merge conflicts * Cleanup some more of the timeouts and sync code * Address code review feedback * Address more code review feedback * Feature/connect cancel (#74) * Implemented connection cancellation * Made connect requests return immediately and created a separate connection complete notification * Fix spelling * Fix sorting * Add separate lock for cancellation source map * Fix an issue with queue deadlocks causing test failures (#77) * Fixed issue where connecting could take very long and cancellation would not work (#78) * Fixed issue where connecting could take very long and cancellation would not work * Addressing feedback * Remove warning suppression * Adding unlimited timeout for query execution (#76) Adding explicitly setting the timeout for command execution to unlimited. We can change this to be user configurable at a later time * Support 'for XML and for JSON' queries (#75) * Set isXMl and isJson for 'for xml/json' resultSets * Change string comparison * Modify if-else * VSTS 8499785. Close SqlToolsService after VS Code exits. (#80) VSTS 8499785. Close SqlToolsService after VS Code exits. * Remove extra level of tasks in binding queue (#79) * Remove extra layer of tasks in binding queue * Change order of assigning result to avoid race condition * Add timeout log for the metadata lock event * Fix test cases
835 lines
34 KiB
C#
835 lines
34 KiB
C#
//
|
|
// Copyright (c) Microsoft. All rights reserved.
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Data.Common;
|
|
using System.Reflection;
|
|
using System.Threading;
|
|
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;
|
|
using Moq.Protected;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.Test.Connection
|
|
{
|
|
/// <summary>
|
|
/// Tests for the ServiceHost Connection Service tests
|
|
/// </summary>
|
|
public class ConnectionServiceTests
|
|
{
|
|
/// <summary>
|
|
/// Creates a mock db command that returns a predefined result set
|
|
/// </summary>
|
|
public static DbCommand CreateTestCommand(Dictionary<string, string>[][] data)
|
|
{
|
|
var commandMock = new Mock<DbCommand> { CallBase = true };
|
|
var commandMockSetup = commandMock.Protected()
|
|
.Setup<DbDataReader>("ExecuteDbDataReader", It.IsAny<CommandBehavior>());
|
|
|
|
commandMockSetup.Returns(new TestDbDataReader(data));
|
|
|
|
return commandMock.Object;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a mock db connection that returns predefined data when queried for a result set
|
|
/// </summary>
|
|
public DbConnection CreateMockDbConnection(Dictionary<string, string>[][] data)
|
|
{
|
|
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
|
connectionMock.Protected()
|
|
.Setup<DbCommand>("CreateDbCommand")
|
|
.Returns(CreateTestCommand(data));
|
|
|
|
return connectionMock.Object;
|
|
}
|
|
|
|
[Fact]
|
|
public void CanCancelConnectRequest()
|
|
{
|
|
var testFile = "file:///my/test/file.sql";
|
|
|
|
// Given a connection that times out and responds to cancellation
|
|
var mockConnection = new Mock<DbConnection> { CallBase = true };
|
|
CancellationToken token;
|
|
bool ready = false;
|
|
mockConnection.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
|
.Callback<CancellationToken>(t =>
|
|
{
|
|
// Pass the token to the return handler and signal the main thread to cancel
|
|
token = t;
|
|
ready = true;
|
|
})
|
|
.Returns(() =>
|
|
{
|
|
if (TestUtils.WaitFor(() => token.IsCancellationRequested))
|
|
{
|
|
throw new OperationCanceledException();
|
|
}
|
|
else
|
|
{
|
|
return Task.FromResult(true);
|
|
}
|
|
});
|
|
|
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
|
.Returns(mockConnection.Object);
|
|
|
|
|
|
var connectionService = new ConnectionService(mockFactory.Object);
|
|
|
|
// Connect the connection asynchronously in a background thread
|
|
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
|
var connectTask = Task.Run(async () =>
|
|
{
|
|
return await connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = testFile,
|
|
Connection = connectionDetails
|
|
});
|
|
});
|
|
|
|
// Wait for the connection to call OpenAsync()
|
|
Assert.True(TestUtils.WaitFor(() => ready));
|
|
|
|
// Send a cancellation request
|
|
var cancelResult = connectionService
|
|
.CancelConnect(new CancelConnectParams()
|
|
{
|
|
OwnerUri = testFile
|
|
});
|
|
|
|
// Wait for the connection task to finish
|
|
connectTask.Wait();
|
|
|
|
// Verify that the connection was cancelled (no connection was created)
|
|
Assert.Null(connectTask.Result.ConnectionId);
|
|
|
|
// Verify that the cancel succeeded
|
|
Assert.True(cancelResult);
|
|
}
|
|
|
|
[Fact]
|
|
public async void CanCancelConnectRequestByConnecting()
|
|
{
|
|
var testFile = "file:///my/test/file.sql";
|
|
|
|
// Given a connection that times out and responds to cancellation
|
|
var mockConnection = new Mock<DbConnection> { CallBase = true };
|
|
CancellationToken token;
|
|
bool ready = false;
|
|
mockConnection.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
|
.Callback<CancellationToken>(t =>
|
|
{
|
|
// Pass the token to the return handler and signal the main thread to cancel
|
|
token = t;
|
|
ready = true;
|
|
})
|
|
.Returns(() =>
|
|
{
|
|
if (TestUtils.WaitFor(() => token.IsCancellationRequested))
|
|
{
|
|
throw new OperationCanceledException();
|
|
}
|
|
else
|
|
{
|
|
return Task.FromResult(true);
|
|
}
|
|
});
|
|
|
|
// Given a second connection that succeeds
|
|
var mockConnection2 = new Mock<DbConnection> { CallBase = true };
|
|
mockConnection2.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
|
.Returns(() => Task.Run(() => {}));
|
|
|
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
mockFactory.SetupSequence(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
|
.Returns(mockConnection.Object)
|
|
.Returns(mockConnection2.Object);
|
|
|
|
|
|
var connectionService = new ConnectionService(mockFactory.Object);
|
|
|
|
// Connect the first connection asynchronously in a background thread
|
|
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
|
var connectTask = Task.Run(async () =>
|
|
{
|
|
return await connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = testFile,
|
|
Connection = connectionDetails
|
|
});
|
|
});
|
|
|
|
// Wait for the connection to call OpenAsync()
|
|
Assert.True(TestUtils.WaitFor(() => ready));
|
|
|
|
// Send a cancellation by trying to connect again
|
|
var connectResult = await connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = testFile,
|
|
Connection = connectionDetails
|
|
});
|
|
|
|
// Wait for the first connection task to finish
|
|
connectTask.Wait();
|
|
|
|
// Verify that the first connection was cancelled (no connection was created)
|
|
Assert.Null(connectTask.Result.ConnectionId);
|
|
|
|
// Verify that the second connection succeeded
|
|
Assert.NotEmpty(connectResult.ConnectionId);
|
|
}
|
|
|
|
[Fact]
|
|
public void CanCancelConnectRequestByDisconnecting()
|
|
{
|
|
var testFile = "file:///my/test/file.sql";
|
|
|
|
// Given a connection that times out and responds to cancellation
|
|
var mockConnection = new Mock<DbConnection> { CallBase = true };
|
|
CancellationToken token;
|
|
bool ready = false;
|
|
mockConnection.Setup(x => x.OpenAsync(Moq.It.IsAny<CancellationToken>()))
|
|
.Callback<CancellationToken>(t =>
|
|
{
|
|
// Pass the token to the return handler and signal the main thread to cancel
|
|
token = t;
|
|
ready = true;
|
|
})
|
|
.Returns(() =>
|
|
{
|
|
if (TestUtils.WaitFor(() => token.IsCancellationRequested))
|
|
{
|
|
throw new OperationCanceledException();
|
|
}
|
|
else
|
|
{
|
|
return Task.FromResult(true);
|
|
}
|
|
});
|
|
|
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
|
.Returns(mockConnection.Object);
|
|
|
|
|
|
var connectionService = new ConnectionService(mockFactory.Object);
|
|
|
|
// Connect the first connection asynchronously in a background thread
|
|
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
|
var connectTask = Task.Run(async () =>
|
|
{
|
|
return await connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = testFile,
|
|
Connection = connectionDetails
|
|
});
|
|
});
|
|
|
|
// Wait for the connection to call OpenAsync()
|
|
Assert.True(TestUtils.WaitFor(() => ready));
|
|
|
|
// Send a cancellation by trying to disconnect
|
|
var disconnectResult = connectionService
|
|
.Disconnect(new DisconnectParams()
|
|
{
|
|
OwnerUri = testFile
|
|
});
|
|
|
|
// Wait for the first connection task to finish
|
|
connectTask.Wait();
|
|
|
|
// Verify that the first connection was cancelled (no connection was created)
|
|
Assert.Null(connectTask.Result.ConnectionId);
|
|
|
|
// Verify that the disconnect failed (since it caused a cancellation)
|
|
Assert.False(disconnectResult);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that we can connect to the default database when no database name is
|
|
/// provided as a parameter.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineDataAttribute(null)]
|
|
[InlineDataAttribute("")]
|
|
public async void CanConnectWithEmptyDatabaseName(string databaseName)
|
|
{
|
|
// Connect
|
|
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
|
connectionDetails.DatabaseName = databaseName;
|
|
var connectionResult = await
|
|
TestObjects.GetTestConnectionService()
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = "file:///my/test/file.sql",
|
|
Connection = connectionDetails
|
|
});
|
|
|
|
// check that a connection was created
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that we can connect to the default database when no database name is
|
|
/// provided as a parameter.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineDataAttribute("master")]
|
|
[InlineDataAttribute("nonMasterDb")]
|
|
public async void ConnectToDefaultDatabaseRespondsWithActualDbName(string expectedDbName)
|
|
{
|
|
// Given connecting with empty database name will return the expected DB name
|
|
var connectionMock = new Mock<DbConnection> { CallBase = true };
|
|
connectionMock.Setup(c => c.Database).Returns(expectedDbName);
|
|
|
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
|
.Returns(connectionMock.Object);
|
|
|
|
var connectionService = new ConnectionService(mockFactory.Object);
|
|
|
|
// When I connect with an empty DB name
|
|
var connectionDetails = TestObjects.GetTestConnectionDetails();
|
|
connectionDetails.DatabaseName = string.Empty;
|
|
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = "file:///my/test/file.sql",
|
|
Connection = connectionDetails
|
|
});
|
|
|
|
// Then I expect connection to succeed and the Summary to include the correct DB name
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
Assert.NotNull(connectionResult.ConnectionSummary);
|
|
Assert.Equal(expectedDbName, connectionResult.ConnectionSummary.DatabaseName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when a connection is started for a URI with an already existing
|
|
/// connection, we disconnect first before connecting.
|
|
/// </summary>
|
|
[Fact]
|
|
public async void ConnectingWhenConnectionExistCausesDisconnectThenConnect()
|
|
{
|
|
bool callbackInvoked = false;
|
|
|
|
// first connect
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionService = TestObjects.GetTestConnectionService();
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that we are connected
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
|
|
// register disconnect callback
|
|
connectionService.RegisterOnDisconnectTask(
|
|
(result, uri) => {
|
|
callbackInvoked = true;
|
|
Assert.True(uri.Equals(ownerUri));
|
|
return Task.FromResult(true);
|
|
}
|
|
);
|
|
|
|
// send annother connect request
|
|
connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that the event was fired (we disconnected first before connecting)
|
|
Assert.True(callbackInvoked);
|
|
|
|
// verify that we connected again
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when connecting with invalid credentials, an error is thrown.
|
|
/// </summary>
|
|
[Fact]
|
|
public async void ConnectingWithInvalidCredentialsYieldsErrorMessage()
|
|
{
|
|
var testConnectionDetails = TestObjects.GetTestConnectionDetails();
|
|
var invalidConnectionDetails = new ConnectionDetails();
|
|
invalidConnectionDetails.ServerName = testConnectionDetails.ServerName;
|
|
invalidConnectionDetails.DatabaseName = testConnectionDetails.DatabaseName;
|
|
invalidConnectionDetails.UserName = "invalidUsername"; // triggers exception when opening mock connection
|
|
invalidConnectionDetails.Password = "invalidPassword";
|
|
|
|
// Connect to test db with invalid credentials
|
|
var connectionResult = await
|
|
TestObjects.GetTestConnectionService()
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = "file://my/sample/file.sql",
|
|
Connection = invalidConnectionDetails
|
|
});
|
|
|
|
// check that an error was caught
|
|
Assert.NotNull(connectionResult.Messages);
|
|
Assert.NotEqual(String.Empty, connectionResult.Messages);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when connecting with invalid parameters, an error is thrown.
|
|
/// </summary>
|
|
[Theory]
|
|
[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 async void ConnectingWithInvalidParametersYieldsErrorMessage(string authType, string ownerUri, string server, string database, string userName, string password)
|
|
{
|
|
// Connect with invalid parameters
|
|
var connectionResult = await
|
|
TestObjects.GetTestConnectionService()
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = new ConnectionDetails() {
|
|
ServerName = server,
|
|
DatabaseName = database,
|
|
UserName = userName,
|
|
Password = password,
|
|
AuthenticationType = authType
|
|
}
|
|
});
|
|
|
|
// check that an error was caught
|
|
Assert.NotNull(connectionResult.Messages);
|
|
Assert.NotEqual(String.Empty, connectionResult.Messages);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when using integrated authentication, the username and/or password can be empty.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData(null, null)]
|
|
[InlineData(null, "")]
|
|
[InlineData("", null)]
|
|
[InlineData("", "")]
|
|
[InlineData("sa", null)]
|
|
[InlineData("sa", "")]
|
|
[InlineData(null, "12345678")]
|
|
[InlineData("", "12345678")]
|
|
public async void ConnectingWithNoUsernameOrPasswordWorksForIntegratedAuth(string userName, string password)
|
|
{
|
|
// Connect
|
|
var connectionResult = await
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that when connecting with a null parameters object, an error is thrown.
|
|
/// </summary>
|
|
[Fact]
|
|
public async void ConnectingWithNullParametersObjectYieldsErrorMessage()
|
|
{
|
|
// Connect with null parameters
|
|
var connectionResult = await
|
|
TestObjects.GetTestConnectionService()
|
|
.Connect(null);
|
|
|
|
// check that an error was caught
|
|
Assert.NotNull(connectionResult.Messages);
|
|
Assert.NotEqual(String.Empty, connectionResult.Messages);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that optional parameters can be built into a connection string for connecting.
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineData("AuthenticationType", "Integrated", "Integrated Security")]
|
|
[InlineData("AuthenticationType", "SqlLogin", "")]
|
|
[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();
|
|
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 parameter is in the connection string
|
|
Assert.True(connectionString.Contains(connectionStringMarker));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that a connection changed event is fired when the database context changes.
|
|
/// </summary>
|
|
[Fact]
|
|
public async void ConnectionChangedEventIsFiredWhenDatabaseContextChanges()
|
|
{
|
|
var serviceHostMock = new Mock<IProtocolEndpoint>();
|
|
|
|
var connectionService = TestObjects.GetTestConnectionService();
|
|
connectionService.ServiceHost = serviceHostMock.Object;
|
|
|
|
// Set up an initial connection
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that a valid connection id was returned
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
|
|
ConnectionInfo info;
|
|
Assert.True(connectionService.TryFindConnection(ownerUri, out info));
|
|
|
|
// Tell the connection manager that the database change ocurred
|
|
connectionService.ChangeConnectionDatabaseContext(ownerUri, "myOtherDb");
|
|
|
|
// Verify that the connection changed event was fired
|
|
serviceHostMock.Verify(x => x.SendEvent<ConnectionChangedParams>(ConnectionChangedNotification.Type, It.IsAny<ConnectionChangedParams>()), Times.Once());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the SQL parser correctly detects errors in text
|
|
/// </summary>
|
|
[Fact]
|
|
public async void ConnectToDatabaseTest()
|
|
{
|
|
// connect to a database instance
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionResult = await
|
|
TestObjects.GetTestConnectionService()
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that a valid connection id was returned
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that we can disconnect from an active connection succesfully
|
|
/// </summary>
|
|
[Fact]
|
|
public async void DisconnectFromDatabaseTest()
|
|
{
|
|
// first connect
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionService = TestObjects.GetTestConnectionService();
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that we are connected
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
|
|
// send disconnect request
|
|
var disconnectResult =
|
|
connectionService
|
|
.Disconnect(new DisconnectParams()
|
|
{
|
|
OwnerUri = ownerUri
|
|
});
|
|
Assert.True(disconnectResult);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that when a disconnect is performed, the callback event is fired
|
|
/// </summary>
|
|
[Fact]
|
|
public async void DisconnectFiresCallbackEvent()
|
|
{
|
|
bool callbackInvoked = false;
|
|
|
|
// first connect
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionService = TestObjects.GetTestConnectionService();
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that we are connected
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
|
|
// register disconnect callback
|
|
connectionService.RegisterOnDisconnectTask(
|
|
(result, uri) => {
|
|
callbackInvoked = true;
|
|
Assert.True(uri.Equals(ownerUri));
|
|
return Task.FromResult(true);
|
|
}
|
|
);
|
|
|
|
// send disconnect request
|
|
var disconnectResult =
|
|
connectionService
|
|
.Disconnect(new DisconnectParams()
|
|
{
|
|
OwnerUri = ownerUri
|
|
});
|
|
Assert.True(disconnectResult);
|
|
|
|
// verify that the event was fired
|
|
Assert.True(callbackInvoked);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that disconnecting an active connection removes the Owner URI -> ConnectionInfo mapping
|
|
/// </summary>
|
|
[Fact]
|
|
public async void DisconnectRemovesOwnerMapping()
|
|
{
|
|
// first connect
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionService = TestObjects.GetTestConnectionService();
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that we are connected
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
|
|
// check that the owner mapping exists
|
|
ConnectionInfo info;
|
|
Assert.True(connectionService.TryFindConnection(ownerUri, out info));
|
|
|
|
// send disconnect request
|
|
var disconnectResult =
|
|
connectionService
|
|
.Disconnect(new DisconnectParams()
|
|
{
|
|
OwnerUri = ownerUri
|
|
});
|
|
Assert.True(disconnectResult);
|
|
|
|
// check that the owner mapping no longer exists
|
|
Assert.False(connectionService.TryFindConnection(ownerUri, out info));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Test that disconnecting validates parameters and doesn't succeed when they are invalid
|
|
/// </summary>
|
|
[Theory]
|
|
[InlineDataAttribute(null)]
|
|
[InlineDataAttribute("")]
|
|
|
|
public async void DisconnectValidatesParameters(string disconnectUri)
|
|
{
|
|
// first connect
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionService = TestObjects.GetTestConnectionService();
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that we are connected
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
|
|
// send disconnect request
|
|
var disconnectResult =
|
|
connectionService
|
|
.Disconnect(new DisconnectParams()
|
|
{
|
|
OwnerUri = disconnectUri
|
|
});
|
|
|
|
// verify that disconnect failed
|
|
Assert.False(disconnectResult);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifies the the list databases operation lists database names for the server used by a connection.
|
|
/// </summary>
|
|
[Fact]
|
|
public async void ListDatabasesOnServerForCurrentConnectionReturnsDatabaseNames()
|
|
{
|
|
// Result set for the query of database names
|
|
Dictionary<string, string>[] data =
|
|
{
|
|
new Dictionary<string, string> { {"name", "master" } },
|
|
new Dictionary<string, string> { {"name", "model" } },
|
|
new Dictionary<string, string> { {"name", "msdb" } },
|
|
new Dictionary<string, string> { {"name", "tempdb" } },
|
|
new Dictionary<string, string> { {"name", "mydatabase" } },
|
|
};
|
|
|
|
// Setup mock connection factory to inject query results
|
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
|
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
|
.Returns(CreateMockDbConnection(new[] {data}));
|
|
var connectionService = new ConnectionService(mockFactory.Object);
|
|
|
|
// connect to a database instance
|
|
string ownerUri = "file://my/sample/file.sql";
|
|
var connectionResult = await
|
|
connectionService
|
|
.Connect(new ConnectParams()
|
|
{
|
|
OwnerUri = ownerUri,
|
|
Connection = TestObjects.GetTestConnectionDetails()
|
|
});
|
|
|
|
// verify that a valid connection id was returned
|
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
|
|
|
// list databases for the connection
|
|
ListDatabasesParams parameters = new ListDatabasesParams();
|
|
parameters.OwnerUri = ownerUri;
|
|
var listDatabasesResult = connectionService.ListDatabases(parameters);
|
|
string[] databaseNames = listDatabasesResult.DatabaseNames;
|
|
|
|
Assert.Equal(databaseNames.Length, 5);
|
|
Assert.Equal(databaseNames[0], "master");
|
|
Assert.Equal(databaseNames[1], "model");
|
|
Assert.Equal(databaseNames[2], "msdb");
|
|
Assert.Equal(databaseNames[3], "tempdb");
|
|
Assert.Equal(databaseNames[4], "mydatabase");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify that the SQL parser correctly detects errors in text
|
|
/// </summary>
|
|
[Fact]
|
|
public async void OnConnectionCallbackHandlerTest()
|
|
{
|
|
bool callbackInvoked = false;
|
|
|
|
// setup connection service with callback
|
|
var connectionService = TestObjects.GetTestConnectionService();
|
|
connectionService.RegisterOnConnectionTask(
|
|
(sqlConnection) => {
|
|
callbackInvoked = true;
|
|
return Task.FromResult(true);
|
|
}
|
|
);
|
|
|
|
// connect to a database instance
|
|
var connectionResult = await connectionService.Connect(TestObjects.GetTestConnectionParams());
|
|
|
|
// verify that a valid connection id was returned
|
|
Assert.True(callbackInvoked);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verify when a connection is created that the URI -> Connection mapping is created in the connection service.
|
|
/// </summary>
|
|
[Fact]
|
|
public async void TestConnectRequestRegistersOwner()
|
|
{
|
|
// Given a request to connect to a database
|
|
var service = TestObjects.GetTestConnectionService();
|
|
var connectParams = TestObjects.GetTestConnectionParams();
|
|
|
|
// connect to a database instance
|
|
var connectionResult = await service.Connect(connectParams);
|
|
|
|
// verify that a valid connection id was returned
|
|
Assert.NotNull(connectionResult.ConnectionId);
|
|
Assert.NotEqual(String.Empty, connectionResult.ConnectionId);
|
|
Assert.NotNull(new Guid(connectionResult.ConnectionId));
|
|
|
|
// verify that the (URI -> connection) mapping was created
|
|
ConnectionInfo info;
|
|
Assert.True(service.TryFindConnection(connectParams.OwnerUri, out info));
|
|
}
|
|
}
|
|
}
|