diff --git a/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/Error.cs b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/Error.cs new file mode 100644 index 00000000..cd1ec1a6 --- /dev/null +++ b/src/Microsoft.SqlTools.Hosting/Hosting/Contracts/Error.cs @@ -0,0 +1,29 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + + +namespace Microsoft.SqlTools.Hosting.Contracts +{ + /// + /// Defines the message contract for errors returned via SendError. + /// + public class Error + { + /// + /// Error code. If omitted will default to 0 + /// + public int Code { get; set; } + + /// + /// Optional information to return with the error + /// + public object Data { get; set; } + + /// + /// Error message + /// + public string Message { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.SqlTools.Hosting/Hosting/Protocol/RequestContext.cs b/src/Microsoft.SqlTools.Hosting/Hosting/Protocol/RequestContext.cs index da675f71..c0889a06 100644 --- a/src/Microsoft.SqlTools.Hosting/Hosting/Protocol/RequestContext.cs +++ b/src/Microsoft.SqlTools.Hosting/Hosting/Protocol/RequestContext.cs @@ -3,8 +3,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // +using System; using System.Threading.Tasks; using Microsoft.SqlTools.Hosting.Protocol.Contracts; +using Error = Microsoft.SqlTools.Hosting.Contracts.Error; using Newtonsoft.Json.Linq; namespace Microsoft.SqlTools.Hosting.Protocol @@ -37,13 +39,26 @@ namespace Microsoft.SqlTools.Hosting.Protocol eventParams); } - public virtual async Task SendError(object errorDetails) + public virtual Task SendError(string errorMessage, int errorCode = 0, object data = null) { - await this.messageWriter.WriteMessage( + // Build the error message + Error error = new Error + { + Message = errorMessage, + Code = errorCode, + Data = data + }; + return this.messageWriter.WriteMessage( Message.ResponseError( requestMessage.Id, requestMessage.Method, - JToken.FromObject(errorDetails))); + JToken.FromObject(error))); + } + + public virtual Task SendError(Exception e) + { + // Overload to use the parameterized error handler + return SendError(e.Message, e.HResult, e.ToString()); } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs index 947ddd4e..a7a639d8 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/Contracts/Definition.cs @@ -14,16 +14,5 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices.Contracts RequestType Type = RequestType.Create("textDocument/definition"); } - - /// - /// Error object for Definition - /// - public class DefinitionError - { - /// - /// Error message - /// - public string message { get; set; } - } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs index 05599bf9..44467a30 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/LanguageServices/LanguageService.cs @@ -319,7 +319,7 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices { if (definitionResult.IsErrorResult) { - await requestContext.SendError( new DefinitionError { message = definitionResult.Message }); + await requestContext.SendError(definitionResult.Message); } else { @@ -820,52 +820,52 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices return result; } - private DefinitionResult GetDefinitionFromTokenList(TextDocumentPosition textDocumentPosition, List tokenList, - ScriptParseInfo scriptParseInfo, ScriptFile scriptFile, ConnectionInfo connInfo) - { - - DefinitionResult lastResult = null; - foreach (var token in tokenList) - { - - // Strip "[" and "]"(if present) from the token text to enable matching with the suggestions. - // The suggestion title does not contain any sql punctuation - string tokenText = TextUtilities.RemoveSquareBracketSyntax(token.Text); - textDocumentPosition.Position.Line = token.StartLocation.LineNumber; - textDocumentPosition.Position.Character = token.StartLocation.ColumnNumber; - if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) - { - try - { - var result = QueueTask(textDocumentPosition, scriptParseInfo, connInfo, scriptFile, tokenText); - lastResult = result; - if (!result.IsErrorResult) - { - return result; - } - } - catch (Exception ex) - { - // if any exceptions are raised return error result with message - Logger.Write(LogLevel.Error, "Exception in GetDefinition " + ex.ToString()); - return new DefinitionResult - { - IsErrorResult = true, - Message = SR.PeekDefinitionError(ex.Message), - Locations = null - }; - } - finally - { - Monitor.Exit(scriptParseInfo.BuildingMetadataLock); - } - } - else - { - Logger.Write(LogLevel.Error, "Timeout waiting to query metadata from server"); - } - } - return (lastResult != null) ? lastResult : null; + private DefinitionResult GetDefinitionFromTokenList(TextDocumentPosition textDocumentPosition, List tokenList, + ScriptParseInfo scriptParseInfo, ScriptFile scriptFile, ConnectionInfo connInfo) + { + + DefinitionResult lastResult = null; + foreach (var token in tokenList) + { + + // Strip "[" and "]"(if present) from the token text to enable matching with the suggestions. + // The suggestion title does not contain any sql punctuation + string tokenText = TextUtilities.RemoveSquareBracketSyntax(token.Text); + textDocumentPosition.Position.Line = token.StartLocation.LineNumber; + textDocumentPosition.Position.Character = token.StartLocation.ColumnNumber; + if (Monitor.TryEnter(scriptParseInfo.BuildingMetadataLock)) + { + try + { + var result = QueueTask(textDocumentPosition, scriptParseInfo, connInfo, scriptFile, tokenText); + lastResult = result; + if (!result.IsErrorResult) + { + return result; + } + } + catch (Exception ex) + { + // if any exceptions are raised return error result with message + Logger.Write(LogLevel.Error, "Exception in GetDefinition " + ex.ToString()); + return new DefinitionResult + { + IsErrorResult = true, + Message = SR.PeekDefinitionError(ex.Message), + Locations = null + }; + } + finally + { + Monitor.Exit(scriptParseInfo.BuildingMetadataLock); + } + } + else + { + Logger.Write(LogLevel.Error, "Timeout waiting to query metadata from server"); + } + } + return (lastResult != null) ? lastResult : null; } /// @@ -898,36 +898,36 @@ namespace Microsoft.SqlTools.ServiceLayer.LanguageServices return null; } - if (scriptParseInfo.IsConnected) - { - //try children tokens first - Stack childrenTokens = selectedToken.Item1; - List tokenList = childrenTokens.ToList(); - DefinitionResult childrenResult = GetDefinitionFromTokenList(textDocumentPosition, tokenList, scriptParseInfo, scriptFile, connInfo); - - // if the children peak definition returned null then - // try the parents - if (childrenResult == null || childrenResult.IsErrorResult) - { - Queue parentTokens = selectedToken.Item2; - tokenList = parentTokens.ToList(); - DefinitionResult parentResult = GetDefinitionFromTokenList(textDocumentPosition, tokenList, scriptParseInfo, scriptFile, connInfo); - return (parentResult == null) ? null : parentResult; - } - else - { - return childrenResult; - } - } - else - { - // User is not connected. - return new DefinitionResult - { - IsErrorResult = true, - Message = SR.PeekDefinitionNotConnectedError, - Locations = null - }; + if (scriptParseInfo.IsConnected) + { + //try children tokens first + Stack childrenTokens = selectedToken.Item1; + List tokenList = childrenTokens.ToList(); + DefinitionResult childrenResult = GetDefinitionFromTokenList(textDocumentPosition, tokenList, scriptParseInfo, scriptFile, connInfo); + + // if the children peak definition returned null then + // try the parents + if (childrenResult == null || childrenResult.IsErrorResult) + { + Queue parentTokens = selectedToken.Item2; + tokenList = parentTokens.ToList(); + DefinitionResult parentResult = GetDefinitionFromTokenList(textDocumentPosition, tokenList, scriptParseInfo, scriptFile, connInfo); + return (parentResult == null) ? null : parentResult; + } + else + { + return childrenResult; + } + } + else + { + // User is not connected. + return new DefinitionResult + { + IsErrorResult = true, + Message = SR.PeekDefinitionNotConnectedError, + Locations = null + }; } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs index 5f79112b..4939a353 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/Contracts/SaveResultsRequest.cs @@ -110,17 +110,6 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts public string Messages { get; set; } } - /// - /// Error object for save result - /// - public class SaveResultRequestError - { - /// - /// Error message - /// - public string message { get; set; } - } - /// /// Request type to save results as CSV /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs index 54fa8f36..9ad9dd79 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/QueryExecutionService.cs @@ -162,7 +162,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution await requestContext.SendResult(new ExecuteRequestResult()); return true; }; - Func queryCreateFailureAction = requestContext.SendError; + Func queryCreateFailureAction = message => requestContext.SendError(message); // Use the internal handler to launch the query return InterServiceExecuteQuery(executeParams, requestContext, queryCreateSuccessAction, queryCreateFailureAction, null, null); @@ -228,7 +228,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { // Setup action for success and failure Func successAction = () => requestContext.SendResult(new QueryDisposeResult()); - Func failureAction = requestContext.SendError; + Func failureAction = message => requestContext.SendError(message); // Use the inter-service dispose functionality await InterServiceDisposeQuery(disposeParams.OwnerUri, successAction, failureAction); @@ -559,10 +559,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution Query query; if (!ActiveQueries.TryGetValue(saveParams.OwnerUri, out query)) { - await requestContext.SendError(new SaveResultRequestError - { - message = SR.QueryServiceQueryInvalidOwnerUri - }); + await requestContext.SendError(SR.QueryServiceQueryInvalidOwnerUri); return; } @@ -574,7 +571,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution ResultSet.SaveAsFailureAsyncEventHandler errorHandler = async (parameters, reason) => { string message = SR.QueryServiceSaveAsFail(Path.GetFileName(parameters.FilePath), reason); - await requestContext.SendError(new SaveResultRequestError { message = message }); + await requestContext.SendError(message); }; try diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Credentials/CredentialServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Credentials/CredentialServiceTests.cs index 3d29d4d7..df6fc9b7 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Credentials/CredentialServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Credentials/CredentialServiceTests.cs @@ -22,31 +22,31 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials /// public class CredentialServiceTests : IDisposable { - private static readonly StoreConfig config = new StoreConfig() + private static readonly StoreConfig Config = new StoreConfig { CredentialFolder = ".testsecrets", CredentialFile = "sqltestsecrets.json", IsRelativeToUserHomeDir = true }; - const string credentialId = "Microsoft_SqlToolsTest_TestId"; - const string password1 = "P@ssw0rd1"; - const string password2 = "2Pass2Furious"; + private const string CredentialId = "Microsoft_SqlToolsTest_TestId"; + private const string Password1 = "P@ssw0rd1"; + private const string Password2 = "2Pass2Furious"; - const string otherCredId = credentialId + "2345"; - const string otherPassword = credentialId + "2345"; + private const string OtherCredId = CredentialId + "2345"; + private const string OtherPassword = CredentialId + "2345"; // Test-owned credential store used to clean up before/after tests to ensure code works as expected // even if previous runs stopped midway through - private ICredentialStore credStore; - private CredentialService service; + private readonly ICredentialStore credStore; + private readonly CredentialService service; /// /// Constructor called once for every test /// public CredentialServiceTests() { - credStore = CredentialService.GetStoreForOS(config); - service = new CredentialService(credStore, config); + credStore = CredentialService.GetStoreForOS(Config); + service = new CredentialService(credStore, Config); DeleteDefaultCreds(); } @@ -57,8 +57,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials private void DeleteDefaultCreds() { - credStore.DeletePassword(credentialId); - credStore.DeletePassword(otherCredId); + credStore.DeletePassword(CredentialId); + credStore.DeletePassword(OtherCredId); #if !WINDOWS_ONLY_BUILD if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) @@ -75,43 +75,43 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials [Fact] public async Task SaveCredentialThrowsIfCredentialIdMissing() { - object errorResponse = null; - var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj); + string errorResponse = null; + var contextMock = RequestContextMocks.Create(null).AddErrorHandling((msg, code, obj) => errorResponse = msg); await service.HandleSaveCredentialRequest(new Credential(null), contextMock.Object); TestUtils.VerifyErrorSent(contextMock); - Assert.True(((string)errorResponse).Contains("ArgumentException")); + Assert.Contains("ArgumentException", errorResponse); } [Fact] public async Task SaveCredentialThrowsIfPasswordMissing() { - object errorResponse = null; - var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj); + string errorResponse = null; + var contextMock = RequestContextMocks.Create(null).AddErrorHandling((msg, code, obj) => errorResponse = msg); - await service.HandleSaveCredentialRequest(new Credential(credentialId), contextMock.Object); + await service.HandleSaveCredentialRequest(new Credential(CredentialId), contextMock.Object); TestUtils.VerifyErrorSent(contextMock); - Assert.True(((string)errorResponse).Contains("ArgumentException")); + Assert.Contains("ArgumentException", errorResponse); } [Fact] public async Task SaveCredentialWorksForSingleCredential() { await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), - verify: (actual => Assert.True(actual))); + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password1), requestContext), + verify: Assert.True); } [Fact] public async Task SaveCredentialSupportsSavingCredentialMultipleTimes() { await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), - verify: (actual => Assert.True(actual))); + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password1), requestContext), + verify: Assert.True); await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), - verify: (actual => Assert.True(actual))); + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password1), requestContext), + verify: Assert.True); } [Fact] @@ -119,16 +119,16 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials { // Given we have saved the credential await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password1), requestContext), verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully"))); // Expect read of the credential to return the password await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext), + test: (requestContext) => service.HandleReadCredentialRequest(new Credential(CredentialId, null), requestContext), verify: (actual => { - Assert.Equal(password1, actual.Password); + Assert.Equal(Password1, actual.Password); })); } @@ -138,25 +138,25 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials // Given we have saved multiple credentials await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password1), requestContext), verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully"))); await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(otherCredId, otherPassword), requestContext), - verify: (actual => Assert.True(actual, "Expect Credential to be saved successfully"))); + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(OtherCredId, OtherPassword), requestContext), + verify: actual => Assert.True(actual, "Expect Credential to be saved successfully")); // Expect read of the credentials to return the right password await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId, null), requestContext), + test: (requestContext) => service.HandleReadCredentialRequest(new Credential(CredentialId, null), requestContext), verify: (actual => { - Assert.Equal(password1, actual.Password); + Assert.Equal(Password1, actual.Password); })); await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleReadCredentialRequest(new Credential(otherCredId, null), requestContext), + test: (requestContext) => service.HandleReadCredentialRequest(new Credential(OtherCredId, null), requestContext), verify: (actual => { - Assert.Equal(otherPassword, actual.Password); + Assert.Equal(OtherPassword, actual.Password); })); } @@ -165,53 +165,53 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials { // Given we have saved twice with a different password await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), - verify: (actual => Assert.True(actual))); + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password1), requestContext), + verify: Assert.True); await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password2), requestContext), - verify: (actual => Assert.True(actual))); + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password2), requestContext), + verify: Assert.True); // When we read the value for this credential // Then we expect only the last saved password to be found await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleReadCredentialRequest(new Credential(credentialId), requestContext), + test: (requestContext) => service.HandleReadCredentialRequest(new Credential(CredentialId), requestContext), verify: (actual => { - Assert.Equal(password2, actual.Password); + Assert.Equal(Password2, actual.Password); })); } [Fact] public async Task ReadCredentialThrowsIfCredentialIsNull() { - object errorResponse = null; - var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj); + string errorResponse = null; + var contextMock = RequestContextMocks.Create(null).AddErrorHandling((msg, code, obj) => errorResponse = msg); // Verify throws on null, and this is sent as an error await service.HandleReadCredentialRequest(null, contextMock.Object); TestUtils.VerifyErrorSent(contextMock); - Assert.True(((string)errorResponse).Contains("ArgumentNullException")); + Assert.Contains("ArgumentNullException", errorResponse); } [Fact] public async Task ReadCredentialThrowsIfIdMissing() { - object errorResponse = null; - var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj); + string errorResponse = null; + var contextMock = RequestContextMocks.Create(null).AddErrorHandling((msg, code, obj) => errorResponse = msg); // Verify throws with no ID await service.HandleReadCredentialRequest(new Credential(), contextMock.Object); TestUtils.VerifyErrorSent(contextMock); - Assert.True(((string)errorResponse).Contains("ArgumentException")); + Assert.Contains("ArgumentException", errorResponse); } [Fact] public async Task ReadCredentialReturnsNullPasswordForMissingCredential() { // Given a credential whose password doesn't exist - string credWithNoPassword = "Microsoft_SqlTools_CredThatDoesNotExist"; - + const string credWithNoPassword = "Microsoft_SqlTools_CredThatDoesNotExist"; + // When reading the credential // Then expect the credential to be returned but password left blank await TestUtils.RunAndVerify( @@ -228,7 +228,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials public async Task DeleteCredentialThrowsIfIdMissing() { object errorResponse = null; - var contextMock = RequestContextMocks.Create(null).AddErrorHandling(obj => errorResponse = obj); + var contextMock = RequestContextMocks.Create(null).AddErrorHandling((msg, code, obj) => errorResponse = msg); // Verify throws with no ID await service.HandleDeleteCredentialRequest(new Credential(), contextMock.Object); @@ -241,18 +241,18 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Credentials { // Save should be true await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(credentialId, password1), requestContext), - verify: (actual => Assert.True(actual))); + test: (requestContext) => service.HandleSaveCredentialRequest(new Credential(CredentialId, Password1), requestContext), + verify: Assert.True); // Then delete - should return true await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext), - verify: (actual => Assert.True(actual))); + test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(CredentialId), requestContext), + verify: Assert.True); // Then delete - should return false as no longer exists await TestUtils.RunAndVerify( - test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(credentialId), requestContext), - verify: (actual => Assert.False(actual))); + test: (requestContext) => service.HandleDeleteCredentialRequest(new Credential(CredentialId), requestContext), + verify: Assert.False); } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs index 30b41f45..4200fc9b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/EditData/ServiceIntegrationTests.cs @@ -265,7 +265,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData // ... And I initialize an edit session with that var efv = new EventFlowValidator() - .AddErrorValidation(Assert.NotNull) + .AddStandardErrorValidation() .Complete(); await eds.HandleInitializeRequest(initParams, efv.Object); @@ -287,16 +287,4 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData return s; } } - - public static class EditServiceEventFlowValidatorExtensions - { - public static EventFlowValidator AddStandardErrorValidation(this EventFlowValidator evf) - { - return evf.AddErrorValidation(p => - { - Assert.NotNull(p); - Assert.NotEmpty(p); - }); - } - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs index 2646802f..5ecd1088 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Formatter/TSqlFormatterServiceTests.cs @@ -154,13 +154,13 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Formatter public static void VerifyResult(Mock> contextMock, Action verify) { contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); - contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); + contextMock.Verify(c => c.SendError(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); verify(); } private static void AssertFormattingEqual(string expected, string actual) { - if (string.Compare(expected, actual) != 0) + if (expected != actual) { StringBuilder error = new StringBuilder(); error.AppendLine("======================"); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs index 4255a889..e13bfd6c 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/LanguageServer/PeekDefinitionTests.cs @@ -93,7 +93,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer requestContext = new Mock>(); requestContext.Setup(rc => rc.SendResult(It.IsAny())) .Returns(Task.FromResult(0)); - requestContext.Setup(rc => rc.SendError(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));; @@ -129,7 +129,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.LanguageServer 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()), Times.Once()); + requestContext.Verify(m => m.SendError(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); } /// diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DisposeTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DisposeTests.cs index 61efd556..f0a8b7ff 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DisposeTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/DisposeTests.cs @@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution var disposeParams = new QueryDisposeParams {OwnerUri = Constants.OwnerUri}; var disposeRequest = new EventFlowValidator() - .AddErrorValidation(Assert.NotEmpty) + .AddStandardErrorValidation() .Complete(); await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs index 53680ef5..7869c708 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/Execution/ServiceIntegrationTests.cs @@ -311,7 +311,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument }; var efv = new EventFlowValidator() - .AddErrorValidation(Assert.NotEmpty) + .AddStandardErrorValidation() .Complete(); await Common.AwaitExecution(queryService, queryParams, efv.Object); @@ -339,7 +339,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution // ... And then I request another query without waiting for the first to complete queryService.ActiveQueries[Constants.OwnerUri].HasExecuted = false; // Simulate query hasn't finished var efv = new EventFlowValidator() - .AddErrorValidation(Assert.NotEmpty) + .AddStandardErrorValidation() .Complete(); await Common.AwaitExecution(queryService, queryParams, efv.Object); @@ -395,7 +395,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.Execution var queryParams = new ExecuteDocumentSelectionParams { OwnerUri = Constants.OwnerUri, QuerySelection = null}; var efv = new EventFlowValidator() - .AddErrorValidation(Assert.NotEmpty) + .AddStandardErrorValidation() .Complete(); await queryService.HandleExecuteRequest(queryParams, efv.Object); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/ExecutionPlanTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/ExecutionPlanTests.cs index 4174b62a..a147b7da 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/ExecutionPlanTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/ExecutionPlanTests.cs @@ -175,7 +175,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Constants.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 }; var executionPlanRequest = new EventFlowValidator() - .AddErrorValidation(Assert.NotNull).Complete(); + .AddStandardErrorValidation() + .Complete(); await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object); executionPlanRequest.Validate(); } @@ -205,7 +206,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution // ... And I then ask for a valid execution plan from it var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Constants.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 }; var executionPlanRequest = new EventFlowValidator() - .AddErrorValidation(Assert.NotNull).Complete(); + .AddStandardErrorValidation() + .Complete(); await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object); executionPlanRequest.Validate(); } @@ -234,7 +236,8 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution // ... And I then ask for an execution plan from a result set var executionPlanParams = new QueryExecutionPlanParams { OwnerUri = Constants.OwnerUri, ResultSetIndex = 0, BatchIndex = 0 }; var executionPlanRequest = new EventFlowValidator() - .AddErrorValidation(Assert.NotNull).Complete(); + .AddStandardErrorValidation() + .Complete(); await queryService.HandleExecutionPlanRequest(executionPlanParams, executionPlanRequest.Object); executionPlanRequest.Validate(); } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SaveResults/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SaveResults/ServiceIntegrationTests.cs index 776e20be..ae18316b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SaveResults/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SaveResults/ServiceIntegrationTests.cs @@ -36,7 +36,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults OwnerUri = Constants.OwnerUri // Won't exist because nothing has executed }; var evf = new EventFlowValidator() - .AddStandardErrorValidator() + .AddStandardErrorValidation() .Complete(); await qes.HandleSaveResultsAsCsvRequest(saveParams, evf.Object); @@ -75,7 +75,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults }; qes.CsvFileFactory = GetCsvStreamFactory(storage, saveParams); var efv = new EventFlowValidator() - .AddStandardErrorValidator() + .AddStandardErrorValidation() .Complete(); @@ -149,7 +149,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults OwnerUri = Constants.OwnerUri // Won't exist because nothing has executed }; var efv = new EventFlowValidator() - .AddStandardErrorValidator() + .AddStandardErrorValidation() .Complete(); await qes.HandleSaveResultsAsJsonRequest(saveParams, efv.Object); @@ -188,7 +188,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults }; qes.JsonFileFactory = GetJsonStreamFactory(storage, saveParams); var efv = new EventFlowValidator() - .AddStandardErrorValidator() + .AddStandardErrorValidation() .Complete(); await qes.HandleSaveResultsAsJsonRequest(saveParams, efv.Object); await qes.ActiveQueries[saveParams.OwnerUri] @@ -280,16 +280,6 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution.SaveResults public static class SaveResultEventFlowValidatorExtensions { - public static EventFlowValidator AddStandardErrorValidator( - this EventFlowValidator efv) - { - return efv.AddErrorValidation(e => - { - Assert.NotNull(e); - Assert.NotNull(e.message); - }); - } - public static EventFlowValidator AddStandardResultValidator( this EventFlowValidator efv) { diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SubsetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SubsetTests.cs index b01792f2..251d2e99 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SubsetTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/QueryExecution/SubsetTests.cs @@ -158,7 +158,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; var subsetRequest = new EventFlowValidator() - .AddErrorValidation(Assert.NotEmpty) + .AddStandardErrorValidation() .Complete(); await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); subsetRequest.Validate(); @@ -180,7 +180,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution // ... And I then ask for a valid set of results from it var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; var subsetRequest = new EventFlowValidator() - .AddErrorValidation(Assert.NotEmpty) + .AddStandardErrorValidation() .Complete(); await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); subsetRequest.Validate(); @@ -201,7 +201,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.QueryExecution // ... And I then ask for a set of results from it var subsetParams = new SubsetParams { OwnerUri = Constants.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; var subsetRequest = new EventFlowValidator() - .AddErrorValidation(Assert.NotEmpty) + .AddStandardErrorValidation() .Complete(); await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); subsetRequest.Validate(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/EventFlowValidator.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/EventFlowValidator.cs index db1d24de..18cbbd59 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/EventFlowValidator.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/EventFlowValidator.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Microsoft.SqlTools.Hosting.Contracts; using Microsoft.SqlTools.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Moq; @@ -25,10 +26,7 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility requestContext = new Mock>(MockBehavior.Strict); } - public RequestContext Object - { - get { return requestContext.Object; } - } + public RequestContext Object => requestContext.Object; public EventFlowValidator AddEventValidation(EventType expectedEvent, Action paramValidation) { @@ -66,19 +64,60 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility return this; } - public EventFlowValidator AddErrorValidation(Action paramValidation) + public EventFlowValidator AddCompleteErrorValidation(Action paramValidation, + Action dataValidation) { + // Put together a validator that checks for null and adds the provided validators + Action validator = e => + { + Assert.NotNull(e); + paramValidation(e.Message, e.Code); + + Assert.IsType(e.Data); + dataValidation((TErrorObj) e.Data); + }; + + // Add the expected error + expectedEvents.Add(new ExpectedEvent + { + EventType = EventTypes.Error, + ParamType = typeof(Error), + Validator = validator + }); + + return this; + } + + public EventFlowValidator AddSimpleErrorValidation(Action paramValidation) + { + // Put together a validator that ensures a null data + Action validator = e => + { + Assert.NotNull(e); + Assert.Null(e.Data); + paramValidation(e.Message, e.Code); + }; + // Add the expected result expectedEvents.Add(new ExpectedEvent { EventType = EventTypes.Error, - ParamType = typeof(TParams), - Validator = paramValidation + ParamType = typeof(Error), + Validator = validator }); return this; } + public EventFlowValidator AddStandardErrorValidation() + { + // Add an error validator that just ensures a non-empty error message and null data obj + return AddSimpleErrorValidation((msg, code) => + { + Assert.NotEmpty(msg); + }); + } + public EventFlowValidator Complete() { // Add general handler for result handling @@ -91,11 +130,11 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility .Returns(Task.FromResult(0)); // Add general handler for error event - requestContext.AddErrorHandling(o => + requestContext.AddErrorHandling((msg, code, obj) => { receivedEvents.Add(new ReceivedEvent { - EventObject = o, + EventObject = new Error {Message = msg, Code = code}, EventType = EventTypes.Error }); }); diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/RequestContextMocks.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/RequestContextMocks.cs index 613e7a83..64ae4041 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/RequestContextMocks.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/RequestContextMocks.cs @@ -48,29 +48,17 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility public static Mock> AddErrorHandling( this Mock> mock, - Action errorCallback) + Action errorCallback) { // Setup the mock for SendError - var sendErrorFlow = mock.Setup(rc => rc.SendError(It.IsAny())) + var sendErrorFlow = mock.Setup(rc => rc.SendError(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(Task.FromResult(0)); if (errorCallback != null) { - sendErrorFlow.Callback(errorCallback); + sendErrorFlow.Callback(errorCallback); } return mock; } - - public static Mock> SetupRequestContextMock( - Action resultCallback, - EventType expectedEvent, - Action, TParams> eventCallback, - Action errorCallback) - { - return Create(resultCallback) - .AddEventHandling(expectedEvent, eventCallback) - .AddErrorHandling(errorCallback); - } - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestUtils.cs index 624e99b1..576eb2f6 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.UnitTests/Utility/TestUtils.cs @@ -40,20 +40,20 @@ namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility public static void VerifyErrorSent(Mock> contextMock) { contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Never); - contextMock.Verify(c => c.SendError(It.IsAny()), Times.Once); + contextMock.Verify(c => c.SendError(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } public static void VerifyResult(Mock> contextMock, U expected, U actual) { contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); Assert.Equal(expected, actual); - contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); + contextMock.Verify(c => c.SendError(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); } public static void VerifyResult(Mock> contextMock, Action verify, T actual) { contextMock.Verify(c => c.SendResult(It.IsAny()), Times.Once); - contextMock.Verify(c => c.SendError(It.IsAny()), Times.Never); + contextMock.Verify(c => c.SendError(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); verify(actual); }