Event Flow Validator (#199)

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
This commit is contained in:
Benjamin Russell
2016-12-20 11:59:03 -08:00
committed by GitHub
parent 0f19de38fb
commit 1e59166147
6 changed files with 481 additions and 615 deletions

View File

@@ -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<ScriptFile>();
fileMock.Setup(file => file.GetLinesInRange(It.IsAny<BufferRange>()))
.Returns(new[] { Common.StandardQuery });
// Set up workspace mock
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.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<QueryExecuteResult, QueryExecuteCompleteParams>(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<QueryExecuteResult>(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<QueryCancelResult>()
.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<ScriptFile>();
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
// Set up workspace mock
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.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<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(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<QueryCancelResult>()
.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<WorkspaceService<SqlToolsSettings>>();
// If:
// ... I request to cancel a query that doesn't exist
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
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<QueryCancelResult>()
.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<RequestContext<QueryCancelResult>> GetQueryCancelResultContextMock(
Action<QueryCancelResult> resultCallback,
Action<object> errorCallback)
{
var requestContext = new Mock<RequestContext<QueryCancelResult>>();
// Setup the mock for SendResult
var sendResultFlow = requestContext
.Setup(rc => rc.SendResult(It.IsAny<QueryCancelResult>()))
.Returns(Task.FromResult(0));
if (resultCallback != null)
{
sendResultFlow.Callback(resultCallback);
}
// Setup the mock for SendError
var sendErrorFlow = requestContext
.Setup(rc => rc.SendError(It.IsAny<object>()))
.Returns(Task.FromResult(0));
if (errorCallback != null)
{
sendErrorFlow.Callback(errorCallback);
}
return requestContext;
}
private static void VerifyQueryCancelCallCount(Mock<RequestContext<QueryCancelResult>> mock,
Times sendResultCalls, Times sendErrorCalls)
{
mock.Verify(rc => rc.SendResult(It.IsAny<QueryCancelResult>()), sendResultCalls);
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
}
#endregion
}
}

View File

@@ -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<ScriptFile>();
fileMock.SetupGet(file => file.Contents).Returns("doesn't matter");
// Set up workspace mock
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.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<QueryExecuteResult, QueryExecuteCompleteParams>(null, QueryExecuteCompleteEvent.Type, null, null);
var executeRequest = RequestContextMocks.Create<QueryExecuteResult>(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<QueryDisposeResult>()
.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<WorkspaceService<SqlToolsSettings>>();
// If:
// ... I attempt to dispose a query that doesn't exist
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
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<QueryDisposeResult>()
.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<ScriptFile>();
fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery);
var workspaceService = new Mock<WorkspaceService<SqlToolsSettings>>();
workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny<string>()))
.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<RequestContext<QueryDisposeResult>> GetQueryDisposeResultContextMock(
Action<QueryDisposeResult> resultCallback,
Action<object> errorCallback)
{
var requestContext = new Mock<RequestContext<QueryDisposeResult>>();
// Setup the mock for SendResult
var sendResultFlow = requestContext
.Setup(rc => rc.SendResult(It.IsAny<QueryDisposeResult>()))
.Returns(Task.FromResult(0));
if (resultCallback != null)
{
sendResultFlow.Callback(resultCallback);
}
// Setup the mock for SendError
var sendErrorFlow = requestContext
.Setup(rc => rc.SendError(It.IsAny<object>()))
.Returns(Task.FromResult(0));
if (errorCallback != null)
{
sendErrorFlow.Callback(errorCallback);
}
return requestContext;
}
private void VerifyQueryDisposeCallCount(Mock<RequestContext<QueryDisposeResult>> mock, Times sendResultCalls,
Times sendErrorCalls)
{
mock.Verify(rc => rc.SendResult(It.IsAny<QueryDisposeResult>()), sendResultCalls);
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
}
#endregion
}
}

View File

@@ -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<SqlToolsSettings>.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<QueryExecuteResult>(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<QueryExecuteResult>()
.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<QueryExecuteResult>()
.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<QueryExecuteResult>(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<QueryExecuteResult>()
.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<QueryExecuteResultSetCompleteParams> resultCompleteParams = new List<QueryExecuteResultSetCompleteParams>();
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(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<QueryExecuteResult>()
.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<QueryExecuteBatchNotificationParams> batchStartParams = new List<QueryExecuteBatchNotificationParams>();
List<QueryExecuteBatchNotificationParams> batchCompleteParams = new List<QueryExecuteBatchNotificationParams>();
List<QueryExecuteResultSetCompleteParams> resultCompleteParams = new List<QueryExecuteResultSetCompleteParams>();
var requestContext = RequestContextMocks.Create<QueryExecuteResult>(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<QueryExecuteResult>()
.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<QueryExecuteResult>(null)
.AddErrorHandling(e => error = e);
await Common.AwaitExecution(queryService, queryParams, requestContext.Object);
var efv = new EventFlowValidator<QueryExecuteResult>()
.AddErrorValidation<string>(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<string>(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<QueryExecuteResult>(null)
.AddErrorHandling(e => error = e);
await Common.AwaitExecution(queryService, queryParams, secondRequestContext.Object);
var efv = new EventFlowValidator<QueryExecuteResult>()
.AddErrorValidation<string>(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<string>(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<QueryExecuteResult>(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<QueryExecuteResult>()
.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<QueryExecuteResult>(null)
.AddErrorHandling(error => errorResult = error);
await queryService.HandleExecuteRequest(queryParams, requestContext.Object);
var efv = new EventFlowValidator<QueryExecuteResult>()
.AddErrorValidation<string>(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<string>(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<QueryExecuteResult>(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<QueryExecuteResult>()
.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<RequestContext<QueryExecuteResult>> mock,
Times sendResultCalls,
Times sendCompletionEventCalls,
Times sendBatchStartEvent,
Times sendBatchCompletionEvent,
Times sendResultCompleteEvent,
Times sendErrorCalls)
private static WorkspaceService<SqlToolsSettings> GetDefaultWorkspaceService(string query)
{
mock.Verify(rc => rc.SendResult(It.IsAny<QueryExecuteResult>()), sendResultCalls);
mock.Verify(rc => rc.SendEvent(
It.Is<EventType<QueryExecuteCompleteParams>>(m => m == QueryExecuteCompleteEvent.Type),
It.IsAny<QueryExecuteCompleteParams>()), sendCompletionEventCalls);
mock.Verify(rc => rc.SendEvent(
It.Is<EventType<QueryExecuteBatchNotificationParams>>(m => m == QueryExecuteBatchCompleteEvent.Type),
It.IsAny<QueryExecuteBatchNotificationParams>()), sendBatchCompletionEvent);
mock.Verify(rc => rc.SendEvent(
It.Is<EventType<QueryExecuteBatchNotificationParams>>(m => m== QueryExecuteBatchStartEvent.Type),
It.IsAny<QueryExecuteBatchNotificationParams>()), sendBatchStartEvent);
mock.Verify(rc => rc.SendEvent(
It.Is<EventType<QueryExecuteResultSetCompleteParams>>(m => m == QueryExecuteResultSetCompleteEvent.Type),
It.IsAny<QueryExecuteResultSetCompleteParams>()), sendResultCompleteEvent);
WorkspaceService<SqlToolsSettings>.Instance.CurrentSettings = new SqlToolsSettings();
var workspaceService = Common.GetPrimedWorkspaceService(query);
return workspaceService;
}
}
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
public static class EventFlowValidatorExtensions
{
public static EventFlowValidator<QueryExecuteResult> AddStandardQueryResultValidator(
this EventFlowValidator<QueryExecuteResult> 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<TRequestContext> AddStandardBatchStartValidator<TRequestContext>(
this EventFlowValidator<TRequestContext> 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<TRequestContext> AddStandardBatchCompleteValidator<TRequestContext>(
this EventFlowValidator<TRequestContext> 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<TRequestContext> AddStandardResultSetValidator<TRequestContext>(
this EventFlowValidator<TRequestContext> 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<TRequestContext> AddStandardQueryCompleteValidator<TRequestContext>(
this EventFlowValidator<TRequestContext> 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);
});
}
}
}

View File

@@ -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<SaveResultRequestResult>()
.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<SaveResultRequestResult>()
.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<SaveResultRequestResult>()
.AddErrorValidation<SaveResultRequestError>(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<SaveResultRequestResult>()
.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<SaveResultRequestResult>()
.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<SaveResultRequestResult>()
.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<SaveResultRequestResult>()
.AddErrorValidation<SaveResultRequestError>(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<SaveResultRequestResult>()
.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
/// <summary>
/// Mock the requestContext for saving a result set
/// </summary>
/// <param name="resultCallback"></param>
/// <param name="errorCallback"></param>
/// <returns></returns>
private static Mock<RequestContext<SaveResultRequestResult>> GetSaveResultsContextMock(
Action<SaveResultRequestResult> resultCallback,
Action<object> errorCallback)
{
var requestContext = RequestContextMocks.Create(resultCallback)
.AddErrorHandling(errorCallback);
return requestContext;
}
/// <summary>
/// Verify the call count for sendResult and error
/// </summary>
/// <param name="mock"></param>
/// <param name="sendResultCalls"></param>
/// <param name="sendErrorCalls"></param>
private static void VerifySaveResultsCallCount(Mock<RequestContext<SaveResultRequestResult>> mock,
Times sendResultCalls, Times sendErrorCalls)
{
mock.Verify(rc => rc.SendResult(It.IsAny<SaveResultRequestResult>()), sendResultCalls);
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
}
#endregion
}
}

View File

@@ -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<QueryExecuteSubsetResult>()
.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<QueryExecuteSubsetResult>()
.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<QueryExecuteSubsetResult>()
.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<QueryExecuteSubsetResult>()
.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<RequestContext<QueryExecuteSubsetResult>> GetQuerySubsetResultContextMock(
Action<QueryExecuteSubsetResult> resultCallback,
Action<object> errorCallback)
{
return RequestContextMocks.Create(resultCallback)
.AddErrorHandling(errorCallback);
}
private static void VerifyQuerySubsetCallCount(Mock<RequestContext<QueryExecuteSubsetResult>> mock, Times sendResultCalls,
Times sendErrorCalls)
{
mock.Verify(rc => rc.SendResult(It.IsAny<QueryExecuteSubsetResult>()), sendResultCalls);
mock.Verify(rc => rc.SendError(It.IsAny<object>()), sendErrorCalls);
}
#endregion
}
}

View File

@@ -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<TRequestContext>
{
private readonly List<ExpectedEvent> expectedEvents = new List<ExpectedEvent>();
private readonly List<ReceivedEvent> receivedEvents = new List<ReceivedEvent>();
private readonly Mock<RequestContext<TRequestContext>> requestContext;
private bool completed;
public EventFlowValidator()
{
requestContext = new Mock<RequestContext<TRequestContext>>(MockBehavior.Strict);
}
public RequestContext<TRequestContext> Object
{
get { return requestContext.Object; }
}
public EventFlowValidator<TRequestContext> AddEventValidation<TParams>(EventType<TParams> expectedEvent, Action<TParams> paramValidation)
{
expectedEvents.Add(new ExpectedEvent
{
EventType = EventTypes.Event,
ParamType = typeof(TParams),
Validator = paramValidation
});
requestContext.Setup(rc => rc.SendEvent(expectedEvent, It.IsAny<TParams>()))
.Callback<EventType<TParams>, TParams>((et, p) =>
{
receivedEvents.Add(new ReceivedEvent
{
EventObject = p,
EventType = EventTypes.Event
});
})
.Returns(Task.FromResult(0));
return this;
}
public EventFlowValidator<TRequestContext> AddResultValidation(Action<TRequestContext> paramValidation)
{
// Add the expected event
expectedEvents.Add(new ExpectedEvent
{
EventType = EventTypes.Result,
ParamType = typeof(TRequestContext),
Validator = paramValidation
});
return this;
}
public EventFlowValidator<TRequestContext> AddErrorValidation<TParams>(Action<TParams> paramValidation)
{
// Add the expected result
expectedEvents.Add(new ExpectedEvent
{
EventType = EventTypes.Error,
ParamType = typeof(TParams),
Validator = paramValidation
});
return this;
}
public EventFlowValidator<TRequestContext> Complete()
{
// Add general handler for result handling
requestContext.Setup(rc => rc.SendResult(It.IsAny<TRequestContext>()))
.Callback<TRequestContext>(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; }
}
}
}