// // 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.IO; using System.Collections.Generic; using System.Threading.Tasks; using System.Runtime.InteropServices; using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.QueryExecution; using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Microsoft.SqlTools.Test.Utility; using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; using Moq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.LanguageServices { /// /// Tests for the language service peek definition/ go to definition feature /// public class PeekDefinitionTests { private const int TaskTimeout = 30000; private readonly string testScriptUri = TestObjects.ScriptUri; private readonly string testConnectionKey = "testdbcontextkey"; private Mock bindingQueue; private Mock> workspaceService; private Mock> requestContext; private Mock binder; private TextDocumentPosition textDocument; private const string OwnerUri = "testFile1"; private void InitializeTestObjects() { // initial cursor position in the script file textDocument = new TextDocumentPosition { TextDocument = new TextDocumentIdentifier {Uri = this.testScriptUri}, Position = new Position { Line = 0, Character = 23 } }; // default settings are stored in the workspace service WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); // set up file for returning the query var fileMock = new Mock(); fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery); fileMock.SetupGet(file => file.ClientFilePath).Returns(this.testScriptUri); // set up workspace mock workspaceService = new Mock>(); workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) .Returns(fileMock.Object); // setup binding queue mock bindingQueue = new Mock(); bindingQueue.Setup(q => q.AddConnectionContext(It.IsAny())) .Returns(this.testConnectionKey); // inject mock instances into the Language Service LanguageService.WorkspaceServiceInstance = workspaceService.Object; LanguageService.ConnectionServiceInstance = TestObjects.GetTestConnectionService(); ConnectionInfo connectionInfo = TestObjects.GetTestConnectionInfo(); LanguageService.ConnectionServiceInstance.OwnerToConnectionMap.Add(this.testScriptUri, connectionInfo); LanguageService.Instance.BindingQueue = bindingQueue.Object; // setup the mock for SendResult requestContext = new Mock>(); requestContext.Setup(rc => rc.SendResult(It.IsAny())) .Returns(Task.FromResult(0)); // setup the IBinder mock binder = new Mock(); binder.Setup(b => b.Bind( It.IsAny>(), It.IsAny(), It.IsAny())); var testScriptParseInfo = new ScriptParseInfo(); LanguageService.Instance.AddOrUpdateScriptParseInfo(this.testScriptUri, testScriptParseInfo); testScriptParseInfo.IsConnected = true; testScriptParseInfo.ConnectionKey = LanguageService.Instance.BindingQueue.AddConnectionContext(connectionInfo); // setup the binding context object ConnectedBindingContext bindingContext = new ConnectedBindingContext(); bindingContext.Binder = binder.Object; bindingContext.MetadataDisplayInfoProvider = new MetadataDisplayInfoProvider(); LanguageService.Instance.BindingQueue.BindingContextMap.Add(testScriptParseInfo.ConnectionKey, bindingContext); } /// /// Tests the definition event handler. When called with no active connection, no definition is sent /// [Fact] public void DefinitionsHandlerWithNoConnectionTest() { InitializeTestObjects(); // request the completion list Task handleCompletion = LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object); handleCompletion.Wait(TaskTimeout); // verify that send result was not called requestContext.Verify(m => m.SendResult(It.IsAny()), Times.Never()); } /// /// Tests creating location objects on windows and non-windows systems /// [Fact] public void GetLocationFromFileForValidFilePathTest() { String filePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "C:\\test\\script.sql" : "/test/script.sql"; PeekDefinition peekDefinition = new PeekDefinition(null); Location[] locations = peekDefinition.GetLocationFromFile(filePath, 0); String expectedFilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "file:///C:/test/script.sql" : "file:/test/script.sql"; Assert.Equal(locations[0].Uri, expectedFilePath); } /// /// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid database name /// [Fact] public void GetSchemaFromDatabaseQualifiedNameWithValidNameTest() { PeekDefinition peekDefinition = new PeekDefinition(null); string validDatabaseQualifiedName = "master.test.test_table"; string objectName = "test_table"; string expectedSchemaName = "test"; string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); Assert.Equal(actualSchemaName, expectedSchemaName); } /// /// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a valid object name and no schema /// [Fact] public void GetSchemaFromDatabaseQualifiedNameWithNoSchemaTest() { PeekDefinition peekDefinition = new PeekDefinition(null); string validDatabaseQualifiedName = "test_table"; string objectName = "test_table"; string expectedSchemaName = "dbo"; string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); Assert.Equal(actualSchemaName, expectedSchemaName); } /// /// Test PeekDefinition.GetSchemaFromDatabaseQualifiedName with a invalid database name /// [Fact] public void GetSchemaFromDatabaseQualifiedNameWithInvalidNameTest() { PeekDefinition peekDefinition = new PeekDefinition(null); string validDatabaseQualifiedName = "x.y.z"; string objectName = "test_table"; string expectedSchemaName = "dbo"; string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); Assert.Equal(actualSchemaName, expectedSchemaName); } #if LIVE_CONNECTION_TESTS /// /// Test get definition for a table object with active connection /// [Fact] public void GetValidTableDefinitionTest() { // Get live connectionInfo ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "test_table"; string schemaName = null; string objectType = "TABLE"; // Get locations for valid table object Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType); Assert.NotNull(locations); Cleanup(locations); } /// /// Test get definition for a invalid table object with active connection /// [Fact] public void GetTableDefinitionInvalidObjectTest() { // Get live connectionInfo ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "test_invalid"; string schemaName = null; string objectType = "TABLE"; // Get locations for invalid table object Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType); Assert.Null(locations); } /// /// Test get definition for a valid table object with schema and active connection /// [Fact] public void GetTableDefinitionWithSchemaTest() { // Get live connectionInfo ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "test_table"; string schemaName = "dbo"; string objectType = "TABLE"; // Get locations for valid table object with schema name Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetTableScripts, objectName, schemaName, objectType); Assert.NotNull(locations); Cleanup(locations); } /// /// Test GetDefinition with an unsupported type(function) /// [Fact] public void GetUnsupportedDefinitionForFullScript() { ScriptFile scriptFile; TextDocumentPosition textDocument = new TextDocumentPosition { TextDocument = new TextDocumentIdentifier { Uri = OwnerUri }, Position = new Position { Line = 0, Character = 20 } }; ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfo(out scriptFile); scriptFile.Contents = "select * from dbo.func ()"; var languageService = LanguageService.Instance; ScriptParseInfo scriptInfo = new ScriptParseInfo { IsConnected = true }; languageService.ScriptParseInfoMap.Add(OwnerUri, scriptInfo); var locations = languageService.GetDefinition(textDocument, scriptFile, connInfo); Assert.Null(locations); } /// /// Test get definition for a view object with active connection /// [Fact] public void GetValidViewDefinitionTest() { ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "objects"; string schemaName = "sys"; string objectType = "VIEW"; Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType); Assert.NotNull(locations); Cleanup(locations); } /// /// Test get definition for an invalid view object with no schema name and with active connection /// [Fact] public void GetViewDefinitionInvalidObjectTest() { ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "objects"; string schemaName = null; string objectType = "VIEW"; Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetViewScripts, objectName, schemaName, objectType); Assert.Null(locations); } /// /// Test get definition for a stored procedure object with active connection /// [Fact] public void GetStoredProcedureDefinitionTest() { ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "SP1"; string schemaName = "dbo"; string objectType = "PROCEDURE"; Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType); Assert.NotNull(locations); Cleanup(locations); } /// /// Test get definition for a stored procedure object that does not exist with active connection /// [Fact] public void GetStoredProcedureDefinitionFailureTest() { ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "SP2"; string schemaName = "dbo"; string objectType = "PROCEDURE"; Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType); Assert.Null(locations); } /// /// Test get definition for a stored procedure object with active connection and no schema /// [Fact] public void GetStoredProcedureDefinitionWithoutSchemaTest() { ConnectionInfo connInfo = TestObjects.InitLiveConnectionInfoForDefinition(); PeekDefinition peekDefinition = new PeekDefinition(connInfo); string objectName = "SP1"; string schemaName = null; string objectType = "PROCEDURE"; Location[] locations = peekDefinition.GetSqlObjectDefinition(peekDefinition.GetStoredProcedureScripts, objectName, schemaName, objectType); Assert.NotNull(locations); Cleanup(locations); } /// /// Helper method to clean up script files /// private void Cleanup(Location[] locations) { Uri fileUri = new Uri(locations[0].Uri); if (File.Exists(fileUri.LocalPath)) { try { File.Delete(fileUri.LocalPath); } catch(Exception) { } } } #endif } }