// // 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.IO; using System.Runtime.InteropServices; using System.Threading.Tasks; using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlServer.Management.SqlParser.Intellisense; using Microsoft.SqlServer.Management.SqlParser.MetadataProvider; using Microsoft.SqlServer.Management.SqlParser.Parser; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.LanguageServices; using Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.Scripting; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace; using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Moq; using Xunit; using GlobalCommon = Microsoft.SqlTools.ServiceLayer.Test.Common; using Location = Microsoft.SqlTools.ServiceLayer.Workspace.Contracts.Location; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer { /// /// 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 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(GlobalCommon.Constants.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(), 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)); requestContext.Setup(rc => rc.SendError(It.IsAny(), It.IsAny(), It.IsAny())).Returns(Task.FromResult(0));; requestContext.Setup(r => r.SendEvent(It.IsAny>(), It.IsAny())).Returns(Task.FromResult(0));; requestContext.Setup(r => r.SendEvent(It.IsAny>(), 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 = false; 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, an error is sent /// [Fact] public async Task DefinitionsHandlerWithNoConnectionTest() { InitializeTestObjects(); // request definition var definitionTask = await Task.WhenAny(LanguageService.HandleDefinitionRequest(textDocument, requestContext.Object), Task.Delay(TaskTimeout)); await definitionTask; // verify that send result was not called and send error was called requestContext.Verify(m => m.SendResult(It.IsAny()), Times.Never()); requestContext.Verify(m => m.SendError(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); } /// /// 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"; Scripter peekDefinition = new Scripter(null, 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() { Scripter peekDefinition = new Scripter(null, 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() { Scripter peekDefinition = new Scripter(null, 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() { Scripter peekDefinition = new Scripter(null, null); string validDatabaseQualifiedName = "x.y.z"; string objectName = "test_table"; string expectedSchemaName = "dbo"; string actualSchemaName = peekDefinition.GetSchemaFromDatabaseQualifiedName(validDatabaseQualifiedName, objectName); Assert.Equal(actualSchemaName, expectedSchemaName); } /// /// Test deletion of peek definition scripts for a valid temp folder that exists /// [Fact] public void DeletePeekDefinitionScriptsTest() { Scripter peekDefinition = new Scripter(null, null); var languageService = LanguageService.Instance; Assert.True(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); languageService.DeletePeekDefinitionScripts(); Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); } /// /// Test deletion of peek definition scripts for a temp folder that does not exist /// [Fact] public void DeletePeekDefinitionScriptsWhenFolderDoesNotExistTest() { var languageService = LanguageService.Instance; Scripter peekDefinition = new Scripter(null, null); FileUtilities.SafeDirectoryDelete(FileUtilities.PeekDefinitionTempFolder, true); Assert.False(Directory.Exists(FileUtilities.PeekDefinitionTempFolder)); // Expected not to throw any exception languageService.DeletePeekDefinitionScripts(); } /// /// Test extracting the full object name from quickInfoText. /// Given a valid object name string and a vaild quickInfo string containing the object name /// Expect the full object name (database.schema.objectName) /// [Fact] public void GetFullObjectNameFromQuickInfoWithValidStringsTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "testTable"; string quickInfoText = "table master.dbo.testTable"; string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); string expected = "master.dbo.testTable"; Assert.Equal(expected, result); } /// /// Test extracting the full object name from quickInfoText with case insensitive comparison. /// Given a valid object name string and a vaild quickInfo string containing the object name /// Expect the full object name (database.schema.objectName) /// [Fact] public void GetFullObjectNameFromQuickInfoWithValidStringsandIgnoreCaseTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "testtable"; string quickInfoText = "table master.dbo.testTable"; string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.OrdinalIgnoreCase); string expected = "master.dbo.testTable"; Assert.Equal(expected, result); } /// /// Test extracting the full object name from quickInfoText. /// Given a null object name string and a vaild quickInfo string containing the object name( and vice versa) /// Expect null /// [Fact] public void GetFullObjectNameFromQuickInfoWithNullStringsTest() { Scripter peekDefinition = new Scripter(null, null); string expected = null; string objectName = null; string quickInfoText = "table master.dbo.testTable"; string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = "tableName"; result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = null; result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); } /// /// Test extracting the full object name from quickInfoText. /// Given a valid object name string and a vaild quickInfo string that does not contain the object name /// Expect null /// [Fact] public void GetFullObjectNameFromQuickInfoWithIncorrectObjectNameTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "test"; string quickInfoText = "table master.dbo.tableName"; string result = peekDefinition.GetFullObjectNameFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); string expected = null; Assert.Equal(expected, result); } /// /// Test extracting the object type from quickInfoText. /// Given a valid object name string and a vaild quickInfo string containing the object name /// Expect correct object type /// [Fact] public void GetTokenTypeFromQuickInfoWithValidStringsTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "tableName"; string quickInfoText = "table master.dbo.tableName"; string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); string expected = "table"; Assert.Equal(expected, result); } /// /// Test extracting the object type from quickInfoText with case insensitive comparison. /// Given a valid object name string and a vaild quickInfo string containing the object name /// Expect correct object type /// [Fact] public void GetTokenTypeFromQuickInfoWithValidStringsandIgnoreCaseTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "tablename"; string quickInfoText = "table master.dbo.tableName"; string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.OrdinalIgnoreCase); string expected = "table"; Assert.Equal(expected, result); } /// /// Test extracting theobject type from quickInfoText. /// Given a null object name string and a vaild quickInfo string containing the object name( and vice versa) /// Expect null /// [Fact] public void GetTokenTypeFromQuickInfoWithNullStringsTest() { Scripter peekDefinition = new Scripter(null, null); string expected = null; string objectName = null; string quickInfoText = "table master.dbo.testTable"; string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = "tableName"; result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); quickInfoText = null; objectName = null; result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); Assert.Equal(expected, result); } /// /// Test extracting the object type from quickInfoText. /// Given a valid object name string and a vaild quickInfo string that does not containthe object name /// Expect null /// [Fact] public void GetTokenTypeFromQuickInfoWithIncorrectObjectNameTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "test"; string quickInfoText = "table master.dbo.tableName"; string result = peekDefinition.GetTokenTypeFromQuickInfo(quickInfoText, objectName, StringComparison.Ordinal); string expected = null; Assert.Equal(expected, result); } /// /// Test getting definition using quickInfo text without a live connection /// Expect an error result (because you cannot script without a live connection) /// [Fact] public void GetDefinitionUsingQuickInfoWithoutConnectionTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "tableName"; string quickInfoText = "table master.dbo.tableName"; DefinitionResult result = peekDefinition.GetDefinitionUsingQuickInfoText(quickInfoText, objectName, null); Assert.NotNull(result); Assert.True(result.IsErrorResult); } /// /// Test getting definition using declaration Type without a live connection /// Expect an error result (because you cannot script without a live connection) /// [Fact] public void GetDefinitionUsingDeclarationItemWithoutConnectionTest() { Scripter peekDefinition = new Scripter(null, null); string objectName = "tableName"; string fullObjectName = "master.dbo.tableName"; DefinitionResult result = peekDefinition.GetDefinitionUsingDeclarationType(DeclarationType.Table, fullObjectName, objectName, null); Assert.NotNull(result); Assert.True(result.IsErrorResult); } } }