diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs index 9bc04a98..a121847d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs @@ -3,14 +3,11 @@ // 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.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace; -using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Moq; using Xunit; @@ -21,131 +18,77 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public async void CancelInProgressQueryTest() { - // Set up file for returning the query - var fileMock = new Mock(); - fileMock.Setup(file => file.GetLinesInRange(It.IsAny())) - .Returns(new[] { Common.StandardQuery }); - // Set up workspace mock - var workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); - // If: // ... I request a query (doesn't matter what kind) and execute it - var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); - var executeParams = new QueryExecuteParams { QuerySelection = Common.SubsectionDocument, OwnerUri = Common.OwnerUri }; - var executeRequest = - RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); + var executeParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri }; + var executeRequest = RequestContextMocks.Create(null); + await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask; queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Fake that it hasn't completed execution // ... And then I request to cancel the query var cancelParams = new QueryCancelParams {OwnerUri = Common.OwnerUri}; - QueryCancelResult result = null; - var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null); + var cancelRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.Null(r.Messages); + }).Complete(); await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object); // Then: - // ... I should have seen a successful event (no messages) - VerifyQueryCancelCallCount(cancelRequest, Times.Once(), Times.Never()); - Assert.Null(result.Messages); - // ... The query should not have been disposed Assert.Equal(1, queryService.ActiveQueries.Count); + cancelRequest.Validate(); } [Fact] public async void CancelExecutedQueryTest() { - - // Set up file for returning the query - var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery); - // Set up workspace mock - var workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); // If: // ... I request a query (doesn't matter what kind) and wait for execution - var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var executeParams = new QueryExecuteParams {QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri}; - var executeRequest = - RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null); + var executeRequest = RequestContextMocks.Create(null); + await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask; // ... And then I request to cancel the query var cancelParams = new QueryCancelParams {OwnerUri = Common.OwnerUri}; - QueryCancelResult result = null; - var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null); + var cancelRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.False(string.IsNullOrWhiteSpace(r.Messages)); + }).Complete(); + await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object); // Then: - // ... I should have seen a result event with an error message - VerifyQueryCancelCallCount(cancelRequest, Times.Once(), Times.Never()); - Assert.NotNull(result.Messages); - // ... The query should not have been disposed Assert.NotEmpty(queryService.ActiveQueries); + cancelRequest.Validate(); } [Fact] public async Task CancelNonExistantTest() { - - var workspaceService = new Mock>(); // If: // ... I request to cancel a query that doesn't exist + var workspaceService = new Mock>(); var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService.Object); - var cancelParams = new QueryCancelParams {OwnerUri = "Doesn't Exist"}; - QueryCancelResult result = null; - var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null); + + var cancelParams = new QueryCancelParams { OwnerUri = "Doesn't Exist" }; + var cancelRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.False(string.IsNullOrWhiteSpace(r.Messages)); + }).Complete(); await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object); - - // Then: - // ... I should have seen a result event with an error message - VerifyQueryCancelCallCount(cancelRequest, Times.Once(), Times.Never()); - Assert.NotNull(result.Messages); + cancelRequest.Validate(); } - - #region Mocking - - private static Mock> GetQueryCancelResultContextMock( - Action resultCallback, - Action errorCallback) - { - var requestContext = new Mock>(); - - // Setup the mock for SendResult - var sendResultFlow = requestContext - .Setup(rc => rc.SendResult(It.IsAny())) - .Returns(Task.FromResult(0)); - if (resultCallback != null) - { - sendResultFlow.Callback(resultCallback); - } - - // Setup the mock for SendError - var sendErrorFlow = requestContext - .Setup(rc => rc.SendError(It.IsAny())) - .Returns(Task.FromResult(0)); - if (errorCallback != null) - { - sendErrorFlow.Callback(errorCallback); - } - - return requestContext; - } - - private static void VerifyQueryCancelCallCount(Mock> mock, - Times sendResultCalls, Times sendErrorCalls) - { - mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); - mock.Verify(rc => rc.SendError(It.IsAny()), sendErrorCalls); - } - - #endregion - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs index 61aee8b8..83ddea3f 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs @@ -3,16 +3,13 @@ // 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.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace; -using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Moq; using Xunit; @@ -38,69 +35,58 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public async void DisposeExecutedQuery() { - // Set up file for returning the query - var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns("doesn't matter"); - // Set up workspace mock - var workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); // If: // ... I request a query (doesn't matter what kind) - var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri}; - var executeRequest = RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null); + var executeRequest = RequestContextMocks.Create(null); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); await queryService.ActiveQueries[Common.OwnerUri].ExecutionTask; // ... And then I dispose of the query var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri}; - QueryDisposeResult result = null; - var disposeRequest = GetQueryDisposeResultContextMock(qdr => { - result = qdr; - }, null); + var disposeRequest = new EventFlowValidator() + .AddResultValidation(r => + { + // Then: Messages should be null + Assert.Null(r.Messages); + }).Complete(); await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object); // Then: - // ... I should have seen a successful result // ... And the active queries should be empty - VerifyQueryDisposeCallCount(disposeRequest, Times.Once(), Times.Never()); - Assert.Null(result.Messages); + disposeRequest.Validate(); Assert.Empty(queryService.ActiveQueries); } [Fact] public async void QueryDisposeMissingQuery() { - var workspaceService = new Mock>(); // If: // ... I attempt to dispose a query that doesn't exist + var workspaceService = new Mock>(); var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService.Object); var disposeParams = new QueryDisposeParams {OwnerUri = Common.OwnerUri}; - QueryDisposeResult result = null; - var disposeRequest = GetQueryDisposeResultContextMock(qdr => result = qdr, null); - await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object); - // Then: - // ... I should have gotten an error result - VerifyQueryDisposeCallCount(disposeRequest, Times.Once(), Times.Never()); - Assert.NotNull(result.Messages); - Assert.NotEmpty(result.Messages); + var disposeRequest = new EventFlowValidator() + .AddResultValidation(r => + { + // Then: Messages should not be null + Assert.NotNull(r.Messages); + Assert.NotEmpty(r.Messages); + }).Complete(); + await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object); + disposeRequest.Validate(); } [Fact] public async Task ServiceDispose() { // Setup: - // ... We need a workspace service that returns a file - var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery); - var workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); // ... We need a query service - var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); - + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); // If: // ... I execute some bogus query @@ -119,44 +105,5 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... There should no longer be an active query Assert.Empty(queryService.ActiveQueries); } - - #region Mocking - - private Mock> GetQueryDisposeResultContextMock( - Action resultCallback, - Action errorCallback) - { - var requestContext = new Mock>(); - - // Setup the mock for SendResult - var sendResultFlow = requestContext - .Setup(rc => rc.SendResult(It.IsAny())) - .Returns(Task.FromResult(0)); - if (resultCallback != null) - { - sendResultFlow.Callback(resultCallback); - } - - // Setup the mock for SendError - var sendErrorFlow = requestContext - .Setup(rc => rc.SendError(It.IsAny())) - .Returns(Task.FromResult(0)); - if (errorCallback != null) - { - sendErrorFlow.Callback(errorCallback); - } - - return requestContext; - } - - private void VerifyQueryDisposeCallCount(Mock> mock, Times sendResultCalls, - Times sendErrorCalls) - { - mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); - mock.Verify(rc => rc.SendError(It.IsAny()), sendErrorCalls); - } - - #endregion - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs index c62211b0..ecea14ab 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs @@ -3,134 +3,89 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.Utility; using Microsoft.SqlTools.ServiceLayer.Workspace; -using Moq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution { public class ServiceIntegrationTests { - [Fact] - public async void QueryExecuteSingleBatchNoResultsTest() + public async Task QueryExecuteAllBatchesNoOp() { - // Given: - // ... Default settings are stored in the workspace service - // ... A workspace with a standard query is configured - WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); - var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); - // If: - // ... I request to execute a valid query with no results + // ... I request to execute a valid query with all batches as no op + var workspaceService = GetDefaultWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Common.NoOpQuery)); var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri }; - QueryExecuteResult result = null; - QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchNotificationParams batchStartParams = null; - QueryExecuteBatchNotificationParams batchCompleteParams = null; - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) - .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, null); - await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + var efv = new EventFlowValidator() + .AddResultValidation(p => + { + Assert.False(string.IsNullOrWhiteSpace(p.Messages)); + }) + .Complete(); + await Common.AwaitExecution(queryService, queryParams, efv.Object); // Then: - // ... No Errors should have been sent - // ... A successful result should have been sent with messages on the first batch - // ... A completion event should have been fired with empty results - // ... A batch completion event should have been fired with empty results - // ... A result set completion event should not have been fired - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); - Assert.Null(result.Messages); + // ... All events should have been called as per their flow validator + efv.Validate(); - Assert.Equal(1, completeParams.BatchSummaries.Length); - Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries); - Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); + // ... There should be one active query + Assert.Equal(1, queryService.ActiveQueries.Count); + } + + [Fact] + public async Task QueryExecuteSingleBatchNoResultsTest() + { + // If: + // ... I request to execute a valid query with no results + var workspaceService = GetDefaultWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); + var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri }; - // ... Batch start summary should not contain result sets, messages, but should contain owner URI - Assert.NotNull(batchStartParams); - Assert.NotNull(batchStartParams.BatchSummary); - Assert.Null(batchStartParams.BatchSummary.Messages); - Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries); - Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri); + var efv = new EventFlowValidator() + .AddStandardQueryResultValidator() + .AddStandardBatchStartValidator() + .AddStandardBatchCompleteValidator() + .AddStandardQueryCompleteValidator(1) + .Complete(); - // ... Batch completion summary should contain result sets, messages, and the owner URI - Assert.NotNull(batchCompleteParams); - Assert.NotNull(batchCompleteParams.BatchSummary); - Assert.Empty(batchCompleteParams.BatchSummary.ResultSetSummaries); - Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); - Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri); + await Common.AwaitExecution(queryService, queryParams, efv.Object); + + // Then: + // ... All events should have been called as per their flow validator + efv.Validate(); // ... There should be one active query Assert.Equal(1, queryService.ActiveQueries.Count); } - [Fact] - public async void QueryExecuteSingleBatchSingleResultTest() + public async Task QueryExecuteSingleBatchSingleResultTest() { - // Given: - // ... A workspace with a standard query is configured - var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); - // If: // ... I request to execute a valid query with results + var workspaceService = GetDefaultWorkspaceService(Common.StandardQuery); var queryService = Common.GetPrimedExecutionService(new[] { Common.StandardTestData }, true, false, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - QueryExecuteResult result = null; - QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchNotificationParams batchStartParams = null; - QueryExecuteBatchNotificationParams batchCompleteParams = null; - QueryExecuteResultSetCompleteParams resultCompleteParams = null; - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) - .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams = p); - await Common.AwaitExecution(queryService, queryParams, requestContext.Object); - + var efv = new EventFlowValidator() + .AddStandardQueryResultValidator() + .AddStandardBatchStartValidator() + .AddStandardResultSetValidator() + .AddStandardBatchCompleteValidator() + .AddStandardQueryCompleteValidator(1) + .Complete(); + await Common.AwaitExecution(queryService, queryParams, efv.Object); + // Then: - // ... No errors should have been sent - // ... A successful result should have been sent without messages - // ... A completion event should have been fired with one result - // ... A batch completion event should have been fired - // ... A resultset completion event should have been fired - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never()); - Assert.Null(result.Messages); - - Assert.Equal(1, completeParams.BatchSummaries.Length); - Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries); - Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); - Assert.False(completeParams.BatchSummaries[0].HasError); - - // ... Batch start summary should not contain result sets, messages, but should contain owner URI - Assert.NotNull(batchStartParams); - Assert.NotNull(batchStartParams.BatchSummary); - Assert.Null(batchStartParams.BatchSummary.Messages); - Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries); - Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri); - - Assert.NotNull(batchCompleteParams); - Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); - Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); - Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri); - - Assert.NotNull(resultCompleteParams); - Assert.Equal(Common.StandardColumns, resultCompleteParams.ResultSetSummary.ColumnInfo.Length); - Assert.Equal(Common.StandardRows, resultCompleteParams.ResultSetSummary.RowCount); - Assert.Equal(Common.OwnerUri, resultCompleteParams.OwnerUri); + // ... All events should have been called as per their flow validator + efv.Validate(); // ... There should be one active query Assert.Equal(1, queryService.ActiveQueries.Count); @@ -139,130 +94,55 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution [Fact] public async Task QueryExecuteSingleBatchMultipleResultTest() { - // Given: - // ... A workspace with a standard query is configured - var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); - // If: // ... I request to execute a valid query with one batch and multiple result sets + var workspaceService = GetDefaultWorkspaceService(Common.StandardQuery); var dataset = new[] { Common.StandardTestData, Common.StandardTestData }; var queryService = Common.GetPrimedExecutionService(dataset, true, false, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - QueryExecuteResult result = null; - QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchNotificationParams batchStartParams = null; - QueryExecuteBatchNotificationParams batchCompleteParams = null; - List resultCompleteParams = new List(); - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p) - .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p)); - await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + var efv = new EventFlowValidator() + .AddStandardQueryResultValidator() + .AddStandardBatchStartValidator() + .AddStandardResultSetValidator() + .AddStandardResultSetValidator() + .AddStandardQueryCompleteValidator(1) + .Complete(); + await Common.AwaitExecution(queryService, queryParams, efv.Object); // Then: - // ... No errors should have been sent - // ... A successful result should have been sent without messages - // ... A completion event should have been fired with one result - // ... A batch completion event should have been fired - // ... Two resultset completion events should have been fired - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Exactly(2), Times.Never()); - Assert.Null(result.Messages); + // ... All events should have been called as per their flow validator + efv.Validate(); - Assert.Equal(1, completeParams.BatchSummaries.Length); - Assert.NotEmpty(completeParams.BatchSummaries[0].ResultSetSummaries); - Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); - Assert.False(completeParams.BatchSummaries[0].HasError); - - // ... Batch start summary should not contain result sets, messages, but should contain owner URI - Assert.NotNull(batchStartParams); - Assert.NotNull(batchStartParams.BatchSummary); - Assert.Null(batchStartParams.BatchSummary.Messages); - Assert.Null(batchStartParams.BatchSummary.ResultSetSummaries); - Assert.Equal(Common.OwnerUri, batchStartParams.OwnerUri); - - Assert.NotNull(batchCompleteParams); - Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); - Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); - Assert.Equal(Common.OwnerUri, batchCompleteParams.OwnerUri); - - Assert.Equal(2, resultCompleteParams.Count); - foreach (var resultParam in resultCompleteParams) - { - Assert.NotNull(resultCompleteParams); - Assert.Equal(Common.StandardColumns, resultParam.ResultSetSummary.ColumnInfo.Length); - Assert.Equal(Common.StandardRows, resultParam.ResultSetSummary.RowCount); - Assert.Equal(Common.OwnerUri, resultParam.OwnerUri); - } + // ... There should be one active query + Assert.Equal(1, queryService.ActiveQueries.Count); } [Fact] public async Task QueryExecuteMultipleBatchSingleResultTest() { - // Given: - // ... A workspace with a standard query is configured - var workspaceService = Common.GetPrimedWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery)); - // If: // ... I request a to execute a valid query with multiple batches + var workspaceService = GetDefaultWorkspaceService(string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery)); var dataSet = new[] { Common.StandardTestData }; var queryService = Common.GetPrimedExecutionService(dataSet, true, false, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - QueryExecuteResult result = null; - QueryExecuteCompleteParams completeParams = null; - List batchStartParams = new List(); - List batchCompleteParams = new List(); - List resultCompleteParams = new List(); - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStartParams.Add(p)) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams.Add(p)) - .AddEventHandling(QueryExecuteResultSetCompleteEvent.Type, (et, p) => resultCompleteParams.Add(p)); - await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + var efv = new EventFlowValidator() + .AddStandardQueryResultValidator() + .AddStandardBatchStartValidator() + .AddStandardResultSetValidator() + .AddStandardBatchCompleteValidator() + .AddStandardBatchCompleteValidator() + .AddStandardResultSetValidator() + .AddStandardBatchCompleteValidator() + .AddStandardQueryCompleteValidator(2) + .Complete(); + await Common.AwaitExecution(queryService, queryParams, efv.Object); // Then: - // ... No errors should have been sent - // ... A successful result should have been sent without messages - - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Exactly(2), Times.Exactly(2), Times.Exactly(2), Times.Never()); - Assert.Null(result.Messages); - - // ... A completion event should have been fired with one two batch summaries, one result each - Assert.Equal(2, completeParams.BatchSummaries.Length); - Assert.Equal(1, completeParams.BatchSummaries[0].ResultSetSummaries.Length); - Assert.Equal(1, completeParams.BatchSummaries[1].ResultSetSummaries.Length); - Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); - Assert.NotEmpty(completeParams.BatchSummaries[1].Messages); - - // ... Two batch start events should have been fired - Assert.Equal(2, batchStartParams.Count); - foreach (var batch in batchStartParams) - { - Assert.Null(batch.BatchSummary.Messages); - Assert.Null(batch.BatchSummary.ResultSetSummaries); - Assert.Equal(Common.OwnerUri, batch.OwnerUri); - } - - // ... Two batch completion events should have been fired - Assert.Equal(2, batchCompleteParams.Count); - foreach (var batch in batchCompleteParams) - { - Assert.NotEmpty(batch.BatchSummary.ResultSetSummaries); - Assert.NotEmpty(batch.BatchSummary.Messages); - Assert.Equal(Common.OwnerUri, batch.OwnerUri); - } - - // ... Two resultset completion events should have been fired - Assert.Equal(2, resultCompleteParams.Count); - foreach (var resultParam in resultCompleteParams) - { - Assert.NotNull(resultParam.ResultSetSummary); - Assert.Equal(Common.StandardColumns, resultParam.ResultSetSummary.ColumnInfo.Length); - Assert.Equal(Common.StandardRows, resultParam.ResultSetSummary.RowCount); - Assert.Equal(Common.OwnerUri, resultParam.OwnerUri); - } + // ... All events should have been called as per their flow validator + efv.Validate(); // ... There should be one active query Assert.Equal(1, queryService.ActiveQueries.Count); @@ -272,39 +152,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution public async void QueryExecuteUnconnectedUriTest() { // Given: - // ... A workspace with a standard query is configured - var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); - // If: // ... I request to execute a query using a file URI that isn't connected + var workspaceService = GetDefaultWorkspaceService(Common.StandardQuery); var queryService = Common.GetPrimedExecutionService(null, false, false, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument }; - object error = null; - var requestContext = RequestContextMocks.Create(null) - .AddErrorHandling(e => error = e); - await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + var efv = new EventFlowValidator() + .AddErrorValidation(Assert.NotEmpty) + .Complete(); + await Common.AwaitExecution(queryService, queryParams, efv.Object); // Then: - // ... An error should have been returned - // ... No result should have been returned - // ... No completion event should have been fired + // ... All events should have been called as per their flow validator + efv.Validate(); + // ... There should be no active queries - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once()); - Assert.IsType(error); - Assert.NotEmpty((string)error); Assert.Empty(queryService.ActiveQueries); } [Fact] public async void QueryExecuteInProgressTest() { - // Given: - // ... A workspace with a standard query is configured - var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); - // If: // ... I request to execute a query + var workspaceService = GetDefaultWorkspaceService(Common.StandardQuery); var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; @@ -314,33 +186,25 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution // ... And then I request another query without waiting for the first to complete queryService.ActiveQueries[Common.OwnerUri].HasExecuted = false; // Simulate query hasn't finished - object error = null; - var secondRequestContext = RequestContextMocks.Create(null) - .AddErrorHandling(e => error = e); - await Common.AwaitExecution(queryService, queryParams, secondRequestContext.Object); + var efv = new EventFlowValidator() + .AddErrorValidation(Assert.NotEmpty) + .Complete(); + await Common.AwaitExecution(queryService, queryParams, efv.Object); // Then: - // ... An error should have been sent - // ... A result should have not have been sent - // ... No completion event should have been fired - // ... A batch completion event should have fired, but not a resultset event + // ... All events should have been called as per their flow validator + efv.Validate(); + // ... There should only be one active query - VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), Times.AtMostOnce(), Times.Never(), Times.Once()); - Assert.IsType(error); - Assert.NotEmpty((string)error); Assert.Equal(1, queryService.ActiveQueries.Count); } - [Fact] public async void QueryExecuteCompletedTest() { - // Given: - // ... A workspace with a standard query is configured - var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); - // If: // ... I request to execute a query + var workspaceService = GetDefaultWorkspaceService(Common.StandardQuery); var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; @@ -349,31 +213,21 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object); // ... And then I request another query after waiting for the first to complete - QueryExecuteResult result = null; - QueryExecuteCompleteParams complete = null; - QueryExecuteBatchNotificationParams batchStart = null; - QueryExecuteBatchNotificationParams batchComplete = null; - var secondRequestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) - .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStart = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); - await Common.AwaitExecution(queryService, queryParams, secondRequestContext.Object); + var efv = new EventFlowValidator() + .AddStandardQueryResultValidator() + .AddStandardBatchStartValidator() + .AddStandardBatchCompleteValidator() + .AddStandardQueryCompleteValidator(1) + .Complete(); + + await Common.AwaitExecution(queryService, queryParams, efv.Object); // Then: - // ... No errors should have been sent - // ... A result should have been sent with no errors + // ... All events should have been called as per their flow validator + efv.Validate(); + // ... There should only be one active query - // ... A batch completion event should have fired, but not a result set completion event - VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); - Assert.Null(result.Messages); - - Assert.False(complete.BatchSummaries.Any(b => b.HasError)); Assert.Equal(1, queryService.ActiveQueries.Count); - - Assert.NotNull(batchStart); - Assert.NotNull(batchComplete); - Assert.False(batchComplete.BatchSummary.HasError); - Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); } [Fact] @@ -388,21 +242,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = null }; - object errorResult = null; - var requestContext = RequestContextMocks.Create(null) - .AddErrorHandling(error => errorResult = error); - await queryService.HandleExecuteRequest(queryParams, requestContext.Object); - + var efv = new EventFlowValidator() + .AddErrorValidation(Assert.NotEmpty) + .Complete(); + await queryService.HandleExecuteRequest(queryParams, efv.Object); // Then: // ... Am error should have been sent - // ... No result should have been sent - // ... No completion events should have been fired - // ... An active query should not have been added - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Never(), Times.Once()); - Assert.NotNull(errorResult); - Assert.IsType(errorResult); - Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys); + efv.Validate(); // ... There should not be an active query Assert.Empty(queryService.ActiveQueries); @@ -411,71 +258,92 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution [Fact] public async void QueryExecuteInvalidQueryTest() { - // Given: - // ... A workspace with a standard query is configured - var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); - // If: // ... I request to execute a query that is invalid + var workspaceService = GetDefaultWorkspaceService(Common.StandardQuery); var queryService = Common.GetPrimedExecutionService(null, true, true, workspaceService); var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - QueryExecuteResult result = null; - QueryExecuteCompleteParams complete = null; - QueryExecuteBatchNotificationParams batchStart = null; - QueryExecuteBatchNotificationParams batchComplete = null; - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) - .AddEventHandling(QueryExecuteBatchStartEvent.Type, (et, p) => batchStart = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); - await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + var efv = new EventFlowValidator() + .AddStandardQueryResultValidator() + .AddStandardBatchStartValidator() + .AddStandardBatchCompleteValidator() + .AddStandardQueryCompleteValidator(1) + .Complete(); + + await Common.AwaitExecution(queryService, queryParams, efv.Object); // Then: - // ... No errors should have been sent - // ... A result should have been sent with success (we successfully started the query) - // ... A completion event (query, batch, not resultset) should have been sent with error - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Once(), Times.Never(), Times.Never()); - Assert.Null(result.Messages); + // ... Am error should have been sent + efv.Validate(); - Assert.Equal(1, complete.BatchSummaries.Length); - Assert.True(complete.BatchSummaries[0].HasError); - Assert.NotEmpty(complete.BatchSummaries[0].Messages); - - Assert.NotNull(batchStart); - Assert.False(batchStart.BatchSummary.HasError); - Assert.Null(batchStart.BatchSummary.Messages); - Assert.Null(batchStart.BatchSummary.ResultSetSummaries); - Assert.Equal(Common.OwnerUri, batchStart.OwnerUri); - - Assert.NotNull(batchComplete); - Assert.True(batchComplete.BatchSummary.HasError); - Assert.NotEmpty(batchComplete.BatchSummary.Messages); - Assert.Equal(Common.OwnerUri, batchComplete.OwnerUri); + // ... There should not be an active query + Assert.Equal(1, queryService.ActiveQueries.Count); } - private static void VerifyQueryExecuteCallCount(Mock> mock, - Times sendResultCalls, - Times sendCompletionEventCalls, - Times sendBatchStartEvent, - Times sendBatchCompletionEvent, - Times sendResultCompleteEvent, - Times sendErrorCalls) + private static WorkspaceService GetDefaultWorkspaceService(string query) { - mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); - mock.Verify(rc => rc.SendEvent( - It.Is>(m => m == QueryExecuteCompleteEvent.Type), - It.IsAny()), sendCompletionEventCalls); - mock.Verify(rc => rc.SendEvent( - It.Is>(m => m == QueryExecuteBatchCompleteEvent.Type), - It.IsAny()), sendBatchCompletionEvent); - mock.Verify(rc => rc.SendEvent( - It.Is>(m => m== QueryExecuteBatchStartEvent.Type), - It.IsAny()), sendBatchStartEvent); - mock.Verify(rc => rc.SendEvent( - It.Is>(m => m == QueryExecuteResultSetCompleteEvent.Type), - It.IsAny()), sendResultCompleteEvent); + WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); + var workspaceService = Common.GetPrimedWorkspaceService(query); + return workspaceService; + } + } - mock.Verify(rc => rc.SendError(It.IsAny()), sendErrorCalls); + public static class EventFlowValidatorExtensions + { + public static EventFlowValidator AddStandardQueryResultValidator( + this EventFlowValidator efv) + { + // We just need to makes sure we get a result back, there's no params to validate + return efv.AddResultValidation(r => + { + Assert.Null(r.Messages); + }); + } + + public static EventFlowValidator AddStandardBatchStartValidator( + this EventFlowValidator efv) + { + return efv.AddEventValidation(QueryExecuteBatchStartEvent.Type, p => + { + // Validate OwnerURI and batch summary is returned + Assert.Equal(Common.OwnerUri, p.OwnerUri); + Assert.NotNull(p.BatchSummary); + }); + } + + public static EventFlowValidator AddStandardBatchCompleteValidator( + this EventFlowValidator efv) + { + return efv.AddEventValidation(QueryExecuteBatchCompleteEvent.Type, p => + { + // Validate OwnerURI and batch summary are returned + Assert.Equal(Common.OwnerUri, p.OwnerUri); + Assert.NotNull(p.BatchSummary); + }); + } + + public static EventFlowValidator AddStandardResultSetValidator( + this EventFlowValidator efv) + { + return efv.AddEventValidation(QueryExecuteResultSetCompleteEvent.Type, p => + { + // Validate OwnerURI and result summary are returned + Assert.Equal(Common.OwnerUri, p.OwnerUri); + Assert.NotNull(p.ResultSetSummary); + }); + } + + public static EventFlowValidator AddStandardQueryCompleteValidator( + this EventFlowValidator efv, int expectedBatches) + { + return efv.AddEventValidation(QueryExecuteCompleteEvent.Type, p => + { + Assert.True(string.IsNullOrWhiteSpace(p.Message)); + Assert.Equal(Common.OwnerUri, p.OwnerUri); + Assert.NotNull(p.BatchSummaries); + Assert.Equal(expectedBatches, p.BatchSummaries.Length); + }); } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs index 5317c5d4..c97e84b8 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs @@ -2,15 +2,12 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. // -using System; using System.IO; using System.Threading.Tasks; using System.Runtime.InteropServices; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.Test.Utility; -using Moq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution @@ -42,8 +39,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution FilePath = "testwrite_1.csv", IncludeHeaders = true }; - SaveResultRequestResult result = null; - var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); + var saveRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.Null(r.Messages); + }).Complete(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object); @@ -51,8 +51,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution await selectedResultSet.GetSaveTask(saveParams.FilePath); // Expect to see a file successfully created in filepath and a success message - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); - Assert.Null(result.Messages); + saveRequest.Validate(); Assert.True(File.Exists(saveParams.FilePath)); // Delete temp file after test @@ -88,8 +87,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution ColumnStartIndex = 0, ColumnEndIndex = 0 }; - SaveResultRequestResult result = null; - var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); + var saveRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.Null(r.Messages); + }).Complete(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object); @@ -98,8 +100,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution await saveTask; // Expect to see a file successfully created in filepath and a success message - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); - Assert.Null(result.Messages); + saveRequest.Validate(); Assert.True(File.Exists(saveParams.FilePath)); // Delete temp file after test @@ -130,9 +131,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution BatchIndex = 0, FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.csv" : "/test.csv" }; - - SaveResultRequestError errMessage = null; - var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (SaveResultRequestError) err); + var saveRequest = new EventFlowValidator() + .AddErrorValidation(e => + { + Assert.False(string.IsNullOrWhiteSpace(e.message)); + }).Complete(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object); @@ -140,8 +143,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution await selectedResultSet.GetSaveTask(saveParams.FilePath); // Expect to see error message - VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once()); - Assert.NotNull(errMessage); + saveRequest.Validate(); Assert.False(File.Exists(saveParams.FilePath)); } @@ -163,13 +165,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution BatchIndex = 0, FilePath = "testwrite_3.csv" }; - SaveResultRequestResult result = null; - var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); + var saveRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.NotNull(r.Messages); + }).Complete(); await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object); // Expect message that save failed - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); - Assert.NotNull(result.Messages); + saveRequest.Validate(); Assert.False(File.Exists(saveParams.FilePath)); } @@ -194,19 +198,20 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution BatchIndex = 0, FilePath = "testwrite_4.json" }; - SaveResultRequestResult result = null; - var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); + var saveRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.Null(r.Messages); + }).Complete(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object); ResultSet selectedResultSet = queryService.ActiveQueries[saveParams.OwnerUri].Batches[saveParams.BatchIndex].ResultSets[saveParams.ResultSetIndex]; - Task saveTask = selectedResultSet.GetSaveTask(saveParams.FilePath); - await saveTask; + await selectedResultSet.GetSaveTask(saveParams.FilePath); // Expect to see a file successfully created in filepath and a success message - Assert.Null(result.Messages); + saveRequest.Validate(); Assert.True(File.Exists(saveParams.FilePath)); - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); // Delete temp file after test if (File.Exists(saveParams.FilePath)) @@ -240,8 +245,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution ColumnStartIndex = 0, ColumnEndIndex = 1 }; - SaveResultRequestResult result = null; - var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); + var saveRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.Null(r.Messages); + }).Complete(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object); @@ -249,8 +257,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution await selectedResultSet.GetSaveTask(saveParams.FilePath); // Expect to see a file successfully created in filepath and a success message - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); - Assert.Null(result.Messages); + saveRequest.Validate(); Assert.True(File.Exists(saveParams.FilePath)); // Delete temp file after test @@ -281,10 +288,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution BatchIndex = 0, FilePath = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "G:\\test.json" : "/test.json" }; - - - SaveResultRequestError errMessage = null; - var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (SaveResultRequestError) err); + var saveRequest = new EventFlowValidator() + .AddErrorValidation(e => + { + Assert.False(string.IsNullOrWhiteSpace(e.message)); + }).Complete(); queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); // Call save results and wait on the save task @@ -293,8 +301,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution await selectedResultSet.GetSaveTask(saveParams.FilePath); // Expect to see error message - Assert.NotNull(errMessage); - VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once()); + saveRequest.Validate(); Assert.False(File.Exists(saveParams.FilePath)); } @@ -316,48 +323,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution BatchIndex = 0, FilePath = "testwrite_6.json" }; - SaveResultRequestResult result = null; - var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); + var saveRequest = new EventFlowValidator() + .AddResultValidation(r => + { + Assert.Equal("Failed to save results, ID not found.", r.Messages); + }).Complete(); await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object); // Expect message that save failed - Assert.Equal("Failed to save results, ID not found.", result.Messages); + saveRequest.Validate(); Assert.False(File.Exists(saveParams.FilePath)); - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); } - - #region Mocking - - /// - /// Mock the requestContext for saving a result set - /// - /// - /// - /// - private static Mock> GetSaveResultsContextMock( - Action resultCallback, - Action errorCallback) - { - var requestContext = RequestContextMocks.Create(resultCallback) - .AddErrorHandling(errorCallback); - - return requestContext; - } - - /// - /// Verify the call count for sendResult and error - /// - /// - /// - /// - private static void VerifySaveResultsCallCount(Mock> mock, - Times sendResultCalls, Times sendErrorCalls) - { - mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); - mock.Verify(rc => rc.SendError(It.IsAny()), sendErrorCalls); - } - - #endregion - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs index d7a5916a..7011768d 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs @@ -7,11 +7,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.Test.Utility; -using Moq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution @@ -140,17 +138,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... And I then ask for a valid set of results from it var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; - QueryExecuteSubsetResult result = null; - var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null); + var subsetRequest = new EventFlowValidator() + .AddResultValidation(r => + { + // Then: Messages should be null and subset should not be null + Assert.Null(r.Message); + Assert.NotNull(r.ResultSubset); + }).Complete(); await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); - - // Then: - // ... I should have a successful result - // ... There should be rows there (other test validate that the rows are correct) - // ... There should not be any error calls - VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never()); - Assert.Null(result.Message); - Assert.NotNull(result.ResultSubset); + subsetRequest.Validate(); } [Fact] @@ -161,17 +157,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; - QueryExecuteSubsetResult result = null; - var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null); + var subsetRequest = new EventFlowValidator() + .AddResultValidation(r => + { + // Then: Messages should not be null and the subset should be null + Assert.NotNull(r.Message); + Assert.Null(r.ResultSubset); + }).Complete(); await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); - - // Then: - // ... I should have an error result - // ... There should be no rows in the result set - // ... There should not be any error calls - VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never()); - Assert.NotNull(result.Message); - Assert.Null(result.ResultSubset); + subsetRequest.Validate(); } [Fact] @@ -189,21 +183,19 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... And I then ask for a valid set of results from it var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; - QueryExecuteSubsetResult result = null; - var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null); + var subsetRequest = new EventFlowValidator() + .AddResultValidation(r => + { + // Then: There should not be a subset and message should not be null + Assert.NotNull(r.Message); + Assert.Null(r.ResultSubset); + }).Complete(); await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); - - // Then: - // ... I should get an error result - // ... There should not be rows - // ... There should not be any error calls - VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never()); - Assert.NotNull(result.Message); - Assert.Null(result.ResultSubset); + subsetRequest.Validate(); } [Fact] - public async void SubsetServiceOutOfRangeSubsetTest() + public async Task SubsetServiceOutOfRangeSubsetTest() { // If: // ... I have a query that doesn't have any result sets @@ -216,39 +208,17 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // ... And I then ask for a set of results from it var subsetParams = new QueryExecuteSubsetParams { OwnerUri = Common.OwnerUri, RowsCount = 1, ResultSetIndex = 0, RowsStartIndex = 0 }; - QueryExecuteSubsetResult result = null; - var subsetRequest = GetQuerySubsetResultContextMock(qesr => result = qesr, null); - queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object).Wait(); - - // Then: - // ... I should get an error result - // ... There should not be rows - // ... There should not be any error calls - VerifyQuerySubsetCallCount(subsetRequest, Times.Once(), Times.Never()); - Assert.NotNull(result.Message); - Assert.Null(result.ResultSubset); + var subsetRequest = new EventFlowValidator() + .AddResultValidation(r => + { + // Then: There should be an error message and no subset + Assert.NotNull(r.Message); + Assert.Null(r.ResultSubset); + }).Complete(); + await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); + subsetRequest.Validate(); } #endregion - - #region Mocking - - private static Mock> GetQuerySubsetResultContextMock( - Action resultCallback, - Action errorCallback) - { - return RequestContextMocks.Create(resultCallback) - .AddErrorHandling(errorCallback); - } - - private static void VerifyQuerySubsetCallCount(Mock> mock, Times sendResultCalls, - Times sendErrorCalls) - { - mock.Verify(rc => rc.SendResult(It.IsAny()), sendResultCalls); - mock.Verify(rc => rc.SendError(It.IsAny()), sendErrorCalls); - } - - #endregion - } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/EventFlowValidator.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/EventFlowValidator.cs new file mode 100644 index 00000000..2e435aa1 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/EventFlowValidator.cs @@ -0,0 +1,163 @@ +// +// 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.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; +using Moq; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Utility +{ + public class EventFlowValidator + { + private readonly List expectedEvents = new List(); + private readonly List receivedEvents = new List(); + private readonly Mock> requestContext; + private bool completed; + + public EventFlowValidator() + { + requestContext = new Mock>(MockBehavior.Strict); + } + + public RequestContext Object + { + get { return requestContext.Object; } + } + + public EventFlowValidator AddEventValidation(EventType expectedEvent, Action paramValidation) + { + expectedEvents.Add(new ExpectedEvent + { + EventType = EventTypes.Event, + ParamType = typeof(TParams), + Validator = paramValidation + }); + + requestContext.Setup(rc => rc.SendEvent(expectedEvent, It.IsAny())) + .Callback, TParams>((et, p) => + { + receivedEvents.Add(new ReceivedEvent + { + EventObject = p, + EventType = EventTypes.Event + }); + }) + .Returns(Task.FromResult(0)); + + return this; + } + + public EventFlowValidator AddResultValidation(Action paramValidation) + { + // Add the expected event + expectedEvents.Add(new ExpectedEvent + { + EventType = EventTypes.Result, + ParamType = typeof(TRequestContext), + Validator = paramValidation + }); + + return this; + } + + public EventFlowValidator AddErrorValidation(Action paramValidation) + { + // Add the expected result + expectedEvents.Add(new ExpectedEvent + { + EventType = EventTypes.Error, + ParamType = typeof(TParams), + Validator = paramValidation + }); + + return this; + } + + public EventFlowValidator Complete() + { + // Add general handler for result handling + requestContext.Setup(rc => rc.SendResult(It.IsAny())) + .Callback(r => receivedEvents.Add(new ReceivedEvent + { + EventObject = r, + EventType = EventTypes.Result + })) + .Returns(Task.FromResult(0)); + + // Add general handler for error event + requestContext.AddErrorHandling(o => + { + receivedEvents.Add(new ReceivedEvent + { + EventObject = o, + EventType = EventTypes.Error + }); + }); + + completed = true; + return this; + } + + public void Validate() + { + // Make sure the handlers have been added + if (!completed) + { + throw new Exception("EventFlowValidator must be completed before it can be validated."); + } + + // Iterate over the two lists in sync to see if they are the same + for (int i = 0; i < Math.Max(expectedEvents.Count, receivedEvents.Count); i++) + { + // Step 0) Make sure both events exist + if (i >= expectedEvents.Count) + { + throw new Exception($"Unexpected event received: [{receivedEvents[i].EventType}] {receivedEvents[i].EventObject}"); + } + ExpectedEvent expected = expectedEvents[i]; + + if (i >= receivedEvents.Count) + { + throw new Exception($"Expected additional events: [{expectedEvents[i].EventType}] {expectedEvents[i].ParamType}"); + } + ReceivedEvent received = receivedEvents[i]; + + // Step 1) Make sure the event type matches + Assert.Equal(expected.EventType, received.EventType); + + // Step 2) Make sure the param type matches + Assert.Equal(expected.ParamType, received.EventObject.GetType()); + + // Step 3) Run the validator on the param object + Assert.NotNull(received.EventObject); + expected.Validator?.DynamicInvoke(received.EventObject); + } + } + + private enum EventTypes + { + Result, + Error, + Event + } + + private class ExpectedEvent + { + public EventTypes EventType { get; set; } + public Type ParamType { get; set; } + public Delegate Validator { get; set; } + } + + private class ReceivedEvent + { + public object EventObject { get; set; } + public EventTypes EventType { get; set; } + } + } +}