mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 10:58:30 -05:00
Added initial tests for the connection manager's intellisense cache
This commit is contained in:
@@ -80,13 +80,36 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConnectionService connectionService = null;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Internal for testing purposes only
|
||||||
|
/// </summary>
|
||||||
|
internal ConnectionService ConnectionServiceInstance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if(connectionService == null)
|
||||||
|
{
|
||||||
|
connectionService = ConnectionService.Instance;
|
||||||
|
}
|
||||||
|
return connectionService;
|
||||||
|
}
|
||||||
|
|
||||||
|
set
|
||||||
|
{
|
||||||
|
connectionService = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void InitializeService(ServiceHost serviceHost)
|
public void InitializeService(ServiceHost serviceHost)
|
||||||
{
|
{
|
||||||
// Register a callback for when a connection is created
|
// Register a callback for when a connection is created
|
||||||
ConnectionService.Instance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
ConnectionServiceInstance.RegisterOnConnectionTask(UpdateAutoCompleteCache);
|
||||||
|
|
||||||
// Register a callback for when a connection is closed
|
// Register a callback for when a connection is closed
|
||||||
ConnectionService.Instance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
ConnectionServiceInstance.RegisterOnDisconnectTask(RemoveAutoCompleteCacheUriReference);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateAutoCompleteCache(ConnectionInfo connectionInfo)
|
private async Task UpdateAutoCompleteCache(ConnectionInfo connectionInfo)
|
||||||
@@ -97,6 +120,14 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Intellisense cache count access for testing.
|
||||||
|
/// </summary>
|
||||||
|
public int GetCacheCount()
|
||||||
|
{
|
||||||
|
return caches.Count;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove a reference to an autocomplete cache from a URI. If
|
/// Remove a reference to an autocomplete cache from a URI. If
|
||||||
/// it is the last URI connected to a particular connection,
|
/// it is the last URI connected to a particular connection,
|
||||||
@@ -157,7 +188,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices
|
|||||||
// that are not backed by a SQL connection
|
// that are not backed by a SQL connection
|
||||||
ConnectionInfo info;
|
ConnectionInfo info;
|
||||||
IntellisenseCache cache;
|
IntellisenseCache cache;
|
||||||
if (ConnectionService.Instance.TryFindConnection(textDocumentPosition.Uri, out info)
|
if (ConnectionServiceInstance.TryFindConnection(textDocumentPosition.Uri, out info)
|
||||||
&& caches.TryGetValue((ConnectionSummary)info.ConnectionDetails, out cache))
|
&& caches.TryGetValue((ConnectionSummary)info.ConnectionDetails, out cache))
|
||||||
{
|
{
|
||||||
return cache.GetAutoCompleteItems(textDocumentPosition).ToArray();
|
return cache.GetAutoCompleteItems(textDocumentPosition).ToArray();
|
||||||
|
|||||||
@@ -3,11 +3,18 @@
|
|||||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Data;
|
||||||
|
using System.Data.Common;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Connection;
|
using Microsoft.SqlTools.ServiceLayer.Connection;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
|
||||||
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
using Microsoft.SqlTools.ServiceLayer.LanguageServices;
|
||||||
|
using Microsoft.SqlTools.ServiceLayer.Test.Utility;
|
||||||
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts;
|
||||||
using Microsoft.SqlTools.Test.Utility;
|
using Microsoft.SqlTools.Test.Utility;
|
||||||
|
using Moq;
|
||||||
|
using Moq.Protected;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
||||||
@@ -19,6 +26,29 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
{
|
{
|
||||||
#region "Diagnostics tests"
|
#region "Diagnostics tests"
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the latest SqlParser (2016 as of this writing) is used by default
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void LatestSqlParserIsUsedByDefault()
|
||||||
|
{
|
||||||
|
// This should only parse correctly on SQL server 2016 or newer
|
||||||
|
const string sql2016Text =
|
||||||
|
@"CREATE SECURITY POLICY [FederatedSecurityPolicy]" + "\r\n" +
|
||||||
|
@"ADD FILTER PREDICATE [rls].[fn_securitypredicate]([CustomerId])" + "\r\n" +
|
||||||
|
@"ON [dbo].[Customer];";
|
||||||
|
|
||||||
|
LanguageService service = TestObjects.GetTestLanguageService();
|
||||||
|
|
||||||
|
// parse
|
||||||
|
var scriptFile = new ScriptFile();
|
||||||
|
scriptFile.SetFileContents(sql2016Text);
|
||||||
|
ScriptFileMarker[] fileMarkers = service.GetSemanticMarkers(scriptFile);
|
||||||
|
|
||||||
|
// verify that no errors are detected
|
||||||
|
Assert.Equal(0, fileMarkers.Length);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verify that the SQL parser correctly detects errors in text
|
/// Verify that the SQL parser correctly detects errors in text
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -108,24 +138,179 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices
|
|||||||
#region "Autocomplete Tests"
|
#region "Autocomplete Tests"
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Verify that the SQL parser correctly detects errors in text
|
/// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that the autocomplete service returns tables for the current connection as suggestions
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AutocompleteTest()
|
public void TablesAreReturnedAsAutocompleteSuggestions()
|
||||||
{
|
{
|
||||||
// TODO Re-enable this test once we have a way to hook up the right auto-complete and connection services.
|
// Result set for the query of database tables
|
||||||
// Probably need a service provider channel so that we can mock service access. Otherwise everything accesses
|
Dictionary<string, string>[] data =
|
||||||
// static instances and cannot be properly tested.
|
{
|
||||||
|
new Dictionary<string, string> { {"name", "master" } },
|
||||||
|
new Dictionary<string, string> { {"name", "model" } }
|
||||||
|
};
|
||||||
|
|
||||||
//var autocompleteService = TestObjects.GetAutoCompleteService();
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||||
//var connectionService = TestObjects.GetTestConnectionService();
|
mockFactory.Setup(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||||
|
.Returns(CreateMockDbConnection(new[] {data}));
|
||||||
|
|
||||||
//ConnectParams connectionRequest = TestObjects.GetTestConnectionParams();
|
var connectionService = TestObjects.GetTestConnectionService();
|
||||||
//var connectionResult = connectionService.Connect(connectionRequest);
|
var autocompleteService = new AutoCompleteService();
|
||||||
|
autocompleteService.ConnectionServiceInstance = connectionService;
|
||||||
|
autocompleteService.InitializeService(Microsoft.SqlTools.ServiceLayer.Hosting.ServiceHost.Instance);
|
||||||
|
|
||||||
//var sqlConnection = connectionService.ActiveConnections[connectionResult.ConnectionId];
|
autocompleteService.ConnectionFactory = mockFactory.Object;
|
||||||
//await autocompleteService.UpdateAutoCompleteCache(sqlConnection);
|
|
||||||
await Task.Run(() => { return; });
|
// Open a connection
|
||||||
|
// The cache should get updated as part of this
|
||||||
|
ConnectParams connectionRequest = TestObjects.GetTestConnectionParams();
|
||||||
|
var connectionResult = connectionService.Connect(connectionRequest);
|
||||||
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
||||||
|
|
||||||
|
// Check that there is one cache created in the auto complete service
|
||||||
|
Assert.Equal(1, autocompleteService.GetCacheCount());
|
||||||
|
|
||||||
|
// Check that we get table suggestions for an autocomplete request
|
||||||
|
TextDocumentPosition position = new TextDocumentPosition();
|
||||||
|
position.Uri = connectionRequest.OwnerUri;
|
||||||
|
position.Position = new Position();
|
||||||
|
position.Position.Line = 1;
|
||||||
|
position.Position.Character = 1;
|
||||||
|
var items = autocompleteService.GetCompletionItems(position);
|
||||||
|
Assert.Equal(2, items.Length);
|
||||||
|
Assert.Equal("master", items[0].Label);
|
||||||
|
Assert.Equal("model", items[1].Label);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that only one intellisense cache is created for two documents using
|
||||||
|
/// the autocomplete service when they share a common connection.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void OnlyOneCacheIsCreatedForTwoDocumentsWithSameConnection()
|
||||||
|
{
|
||||||
|
var connectionService = TestObjects.GetTestConnectionService();
|
||||||
|
var autocompleteService = new AutoCompleteService();
|
||||||
|
autocompleteService.ConnectionServiceInstance = connectionService;
|
||||||
|
autocompleteService.InitializeService(Microsoft.SqlTools.ServiceLayer.Hosting.ServiceHost.Instance);
|
||||||
|
|
||||||
|
// Open two connections
|
||||||
|
ConnectParams connectionRequest1 = TestObjects.GetTestConnectionParams();
|
||||||
|
connectionRequest1.OwnerUri = "file:///my/first/file.sql";
|
||||||
|
ConnectParams connectionRequest2 = TestObjects.GetTestConnectionParams();
|
||||||
|
connectionRequest2.OwnerUri = "file:///my/second/file.sql";
|
||||||
|
var connectionResult1 = connectionService.Connect(connectionRequest1);
|
||||||
|
Assert.NotEmpty(connectionResult1.ConnectionId);
|
||||||
|
var connectionResult2 = connectionService.Connect(connectionRequest2);
|
||||||
|
Assert.NotEmpty(connectionResult2.ConnectionId);
|
||||||
|
|
||||||
|
// Verify that only one intellisense cache is created to service both URI's
|
||||||
|
Assert.Equal(1, autocompleteService.GetCacheCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Verify that two different intellisense caches and corresponding autocomplete
|
||||||
|
/// suggestions are provided for two documents with different connections.
|
||||||
|
/// </summary>
|
||||||
|
[Fact]
|
||||||
|
public void TwoCachesAreCreatedForTwoDocumentsWithDifferentConnections()
|
||||||
|
{
|
||||||
|
// Result set for the query of database tables
|
||||||
|
Dictionary<string, string>[] data1 =
|
||||||
|
{
|
||||||
|
new Dictionary<string, string> { {"name", "master" } },
|
||||||
|
new Dictionary<string, string> { {"name", "model" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
Dictionary<string, string>[] data2 =
|
||||||
|
{
|
||||||
|
new Dictionary<string, string> { {"name", "master" } },
|
||||||
|
new Dictionary<string, string> { {"name", "my_table" } },
|
||||||
|
new Dictionary<string, string> { {"name", "my_other_table" } }
|
||||||
|
};
|
||||||
|
|
||||||
|
var mockFactory = new Mock<ISqlConnectionFactory>();
|
||||||
|
mockFactory.SetupSequence(factory => factory.CreateSqlConnection(It.IsAny<string>()))
|
||||||
|
.Returns(CreateMockDbConnection(new[] {data1}))
|
||||||
|
.Returns(CreateMockDbConnection(new[] {data2}));
|
||||||
|
|
||||||
|
var connectionService = TestObjects.GetTestConnectionService();
|
||||||
|
var autocompleteService = new AutoCompleteService();
|
||||||
|
autocompleteService.ConnectionServiceInstance = connectionService;
|
||||||
|
autocompleteService.InitializeService(Microsoft.SqlTools.ServiceLayer.Hosting.ServiceHost.Instance);
|
||||||
|
|
||||||
|
autocompleteService.ConnectionFactory = mockFactory.Object;
|
||||||
|
|
||||||
|
// Open connections
|
||||||
|
// The cache should get updated as part of this
|
||||||
|
ConnectParams connectionRequest = TestObjects.GetTestConnectionParams();
|
||||||
|
connectionRequest.OwnerUri = "file:///my/first/sql/file.sql";
|
||||||
|
var connectionResult = connectionService.Connect(connectionRequest);
|
||||||
|
Assert.NotEmpty(connectionResult.ConnectionId);
|
||||||
|
|
||||||
|
// Check that there is one cache created in the auto complete service
|
||||||
|
Assert.Equal(1, autocompleteService.GetCacheCount());
|
||||||
|
|
||||||
|
// Open second connection
|
||||||
|
ConnectParams connectionRequest2 = TestObjects.GetTestConnectionParams();
|
||||||
|
connectionRequest2.OwnerUri = "file:///my/second/sql/file.sql";
|
||||||
|
connectionRequest2.Connection.DatabaseName = "my_other_db";
|
||||||
|
var connectionResult2 = connectionService.Connect(connectionRequest2);
|
||||||
|
Assert.NotEmpty(connectionResult2.ConnectionId);
|
||||||
|
|
||||||
|
// Check that there are now two caches in the auto complete service
|
||||||
|
Assert.Equal(2, autocompleteService.GetCacheCount());
|
||||||
|
|
||||||
|
// Check that we get 2 different table suggestions for autocomplete requests
|
||||||
|
TextDocumentPosition position = new TextDocumentPosition();
|
||||||
|
position.Uri = connectionRequest.OwnerUri;
|
||||||
|
position.Position = new Position();
|
||||||
|
position.Position.Line = 1;
|
||||||
|
position.Position.Character = 1;
|
||||||
|
|
||||||
|
var items = autocompleteService.GetCompletionItems(position);
|
||||||
|
Assert.Equal(2, items.Length);
|
||||||
|
Assert.Equal("master", items[0].Label);
|
||||||
|
Assert.Equal("model", items[1].Label);
|
||||||
|
|
||||||
|
TextDocumentPosition position2 = new TextDocumentPosition();
|
||||||
|
position2.Uri = connectionRequest2.OwnerUri;
|
||||||
|
position2.Position = new Position();
|
||||||
|
position2.Position.Line = 1;
|
||||||
|
position2.Position.Character = 1;
|
||||||
|
|
||||||
|
var items2 = autocompleteService.GetCompletionItems(position2);
|
||||||
|
Assert.Equal(3, items2.Length);
|
||||||
|
Assert.Equal("master", items2[0].Label);
|
||||||
|
Assert.Equal("my_table", items2[1].Label);
|
||||||
|
Assert.Equal("my_other_table", items2[2].Label);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|||||||
Reference in New Issue
Block a user