From 1e59166147bfc565be6368d3ad59aa14fefd5d89 Mon Sep 17 00:00:00 2001 From: Benjamin Russell Date: Tue, 20 Dec 2016 11:59:03 -0800 Subject: [PATCH] Event Flow Validator (#199) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a reworking of the unit tests to permit us to better test events from the service host. This new Event Flow Validator class allows creating a chain of events that can then be validated after execution of the request. Each event can have its own custom validation logic for verifying that the object sent via the service host is correct. It also allows us to validate that the order of events are correct. The big drawback is that (at this time) the validator cannot support asynchronous events or non-determinant ordering of events. We don't need this for the query execution functionality despite messages being sent asynchronously because async messages aren't sent during unit tests (due to the db message event only being present on SqlDbConnection classes). If the need arises to do async or out of order event validation, then I have some ideas for how we can do that. * Applying the event flow validator to the query execution service integration tests * Undoing changes to events that were included in cherry-picked commit * Cleaning up event flow validation to query execution * Add efv to cancel tests * Adding efv to dispose tests * Adding efv to subset tests * Adding efv to SaveResults tests * Copyright --- .../QueryExecution/CancelTests.cs | 119 ++--- .../QueryExecution/DisposeTests.cs | 97 +--- .../Execution/ServiceIntegrationTests.cs | 496 +++++++----------- .../QueryExecution/SaveResultsTests.cs | 123 ++--- .../QueryExecution/SubsetTests.cs | 98 ++-- .../Utility/EventFlowValidator.cs | 163 ++++++ 6 files changed, 481 insertions(+), 615 deletions(-) create mode 100644 test/Microsoft.SqlTools.ServiceLayer.Test/Utility/EventFlowValidator.cs 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; } + } + } +}