diff --git a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs index 0d4ab4b0..16982606 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Connection/ConnectionService.cs @@ -77,9 +77,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } /// - /// Default constructor is private since it's a singleton class + /// Default constructor should be private since it's a singleton class, but we need a constructor + /// for use in unit test mocking. /// - private ConnectionService() + public ConnectionService() { } @@ -129,7 +130,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Connection } // Attempts to link a URI to an actively used connection for this URI - public bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo) + public virtual bool TryFindConnection(string ownerUri, out ConnectionInfo connectionInfo) { return this.ownerToConnectionMap.TryGetValue(ownerUri, out connectionInfo); } diff --git a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs index a96de759..a74a38d1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/QueryExecution/ResultSet.cs @@ -8,7 +8,6 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Common; using System.Linq; -using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; @@ -17,6 +16,10 @@ using Microsoft.SqlTools.ServiceLayer.Utility; namespace Microsoft.SqlTools.ServiceLayer.QueryExecution { + /// + /// Class that represents a resultset the was generated from a query. Contains logic for + /// storing and retrieving results. Is contained by a Batch class. + /// public class ResultSet : IDisposable { #region Constants @@ -35,11 +38,21 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution #region Member Variables + /// + /// The reader to use for this resultset + /// + private readonly StorageDataReader dataReader; + /// /// For IDisposable pattern, whether or not object has been disposed /// private bool disposed; + /// + /// A list of offsets into the buffer file that correspond to where rows start + /// + private readonly LongList fileOffsets; + /// /// The factory to use to get reading/writing handlers /// @@ -63,12 +76,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// /// Whether the resultSet is in the process of being disposed /// - private bool isBeingDisposed; - - /// - /// All save tasks currently saving this ResultSet - /// - private ConcurrentDictionary saveTasks; + private readonly ConcurrentDictionary saveTasks; #endregion @@ -82,11 +90,11 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution // Sanity check to make sure we got a reader Validate.IsNotNull(nameof(reader), SR.QueryServiceResultSetReaderNull); - DataReader = new StorageDataReader(reader); + dataReader = new StorageDataReader(reader); // Initialize the storage outputFileName = factory.CreateFile(); - FileOffsets = new LongList(); + fileOffsets = new LongList(); // Store the factory fileStreamFactory = factory; @@ -100,29 +108,13 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution /// Whether the resultSet is in the process of being disposed /// /// - internal bool IsBeingDisposed - { - get - { - return isBeingDisposed; - } - } + internal bool IsBeingDisposed { get; private set; } /// /// The columns for this result set /// public DbColumnWrapper[] Columns { get; private set; } - /// - /// The reader to use for this resultset - /// - private StorageDataReader DataReader { get; set; } - - /// - /// A list of offsets into the buffer file that correspond to where rows start - /// - private LongList FileOffsets { get; set; } - /// /// Maximum number of characters to store for a field /// @@ -178,14 +170,14 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution if (isSingleColumnXmlJsonResultSet) { // Iterate over all the rows and process them into a list of string builders - IEnumerable rowValues = FileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue); + IEnumerable rowValues = fileOffsets.Select(rowOffset => fileStreamReader.ReadRow(rowOffset, Columns)[0].DisplayValue); rows = new[] { new[] { string.Join(string.Empty, rowValues) } }; } else { // Figure out which rows we need to read back - IEnumerable rowOffsets = FileOffsets.Skip(startRow).Take(rowCount); + IEnumerable rowOffsets = fileOffsets.Skip(startRow).Take(rowCount); // Iterate over the rows we need and process them into output rows = rowOffsets.Select(rowOffset => @@ -216,18 +208,22 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution using (IFileStreamWriter fileWriter = fileStreamFactory.GetWriter(outputFileName, MaxCharsToStore, MaxXmlCharsToStore)) { // If we can initialize the columns using the column schema, use that - if (!DataReader.DbDataReader.CanGetColumnSchema()) + if (!dataReader.DbDataReader.CanGetColumnSchema()) { throw new InvalidOperationException(SR.QueryServiceResultSetNoColumnSchema); } - Columns = DataReader.Columns; - long currentFileOffset = 0; + Columns = dataReader.Columns; - while (await DataReader.ReadAsync(cancellationToken)) + long currentFileOffset = 0; + while (await dataReader.ReadAsync(cancellationToken)) { + // Store the beginning of the row + long rowStart = currentFileOffset; + currentFileOffset += fileWriter.WriteRow(dataReader); + + // Add the row to the list of rows we have only if the row was successfully written RowCount++; - FileOffsets.Add(currentFileOffset); - currentFileOffset += fileWriter.WriteRow(DataReader); + fileOffsets.Add(rowStart); } } // Check if resultset is 'for xml/json'. If it is, set isJson/isXml value in column metadata @@ -251,7 +247,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution return; } - isBeingDisposed = true; + IsBeingDisposed = true; // Check if saveTasks are running for this ResultSet if (!saveTasks.IsEmpty) { @@ -263,7 +259,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution fileStreamFactory.DisposeFile(outputFileName); } disposed = true; - isBeingDisposed = false; + IsBeingDisposed = false; }); } else @@ -274,7 +270,7 @@ namespace Microsoft.SqlTools.ServiceLayer.QueryExecution fileStreamFactory.DisposeFile(outputFileName); } disposed = true; - isBeingDisposed = false; + IsBeingDisposed = false; } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs index 29afec38..3be28f64 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/CancelTests.cs @@ -32,7 +32,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // If: // ... I request a query (doesn't matter what kind) and execute it - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); + 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); @@ -44,7 +44,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var cancelParams = new QueryCancelParams {OwnerUri = Common.OwnerUri}; QueryCancelResult result = null; var cancelRequest = GetQueryCancelResultContextMock(qcr => result = qcr, null); - queryService.HandleCancelRequest(cancelParams, cancelRequest.Object).Wait(); + await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object); // Then: // ... I should have seen a successful event (no messages) @@ -68,7 +68,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution .Returns(fileMock.Object); // If: // ... I request a query (doesn't matter what kind) and wait for execution - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); var executeParams = new QueryExecuteParams {QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri}; var executeRequest = RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null); @@ -97,11 +97,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var workspaceService = new Mock>(); // If: // ... I request to cancel a query that doesn't exist - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object); + 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); - queryService.HandleCancelRequest(cancelParams, cancelRequest.Object).Wait(); + await queryService.HandleCancelRequest(cancelParams, cancelRequest.Object); // Then: // ... I should have seen a result event with an error message diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs index 0747792f..c8f8c15f 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Common.cs @@ -6,16 +6,12 @@ using System; using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; using System.IO; using System.Threading; using System.Threading.Tasks; -using Microsoft.SqlServer.Management.Common; -using Microsoft.SqlServer.Management.SmoMetadataProvider; -using Microsoft.SqlServer.Management.SqlParser.Binder; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Connection.Contracts; -using Microsoft.SqlTools.ServiceLayer.LanguageServices; +using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; using Microsoft.SqlTools.ServiceLayer.QueryExecution; using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution.DataStorage; @@ -71,7 +67,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public static Batch GetBasicExecutedBatch() { - Batch batch = new Batch(StandardQuery, SubsectionDocument, 1, GetFileStreamFactory()); + Batch batch = new Batch(StandardQuery, SubsectionDocument, 1, GetFileStreamFactory(new Dictionary())); batch.Execute(CreateTestConnection(new[] {StandardTestData}, false), CancellationToken.None).Wait(); return batch; } @@ -79,7 +75,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public static Query GetBasicExecutedQuery() { ConnectionInfo ci = CreateTestConnectionInfo(new[] {StandardTestData}, false); - Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory()); + Query query = new Query(StandardQuery, ci, new QueryExecutionSettings(), GetFileStreamFactory(new Dictionary())); query.Execute(); query.ExecutionTask.Wait(); return query; @@ -101,18 +97,35 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution return output; } + public static async Task AwaitExecution(QueryExecutionService service, QueryExecuteParams qeParams, + RequestContext requestContext) + { + await service.HandleExecuteRequest(qeParams, requestContext); + if (service.ActiveQueries.ContainsKey(qeParams.OwnerUri) && service.ActiveQueries[qeParams.OwnerUri].ExecutionTask != null) + { + await service.ActiveQueries[qeParams.OwnerUri].ExecutionTask; + } + } + #endregion #region FileStreamWriteMocking - public static IFileStreamFactory GetFileStreamFactory() + public static IFileStreamFactory GetFileStreamFactory(Dictionary storage) { Mock mock = new Mock(); + mock.Setup(fsf => fsf.CreateFile()) + .Returns(() => + { + string fileName = Guid.NewGuid().ToString(); + storage.Add(fileName, new byte[8192]); + return fileName; + }); mock.Setup(fsf => fsf.GetReader(It.IsAny())) - .Returns(new ServiceBufferFileStreamReader(new InMemoryWrapper(), It.IsAny())); + .Returns(output => new ServiceBufferFileStreamReader(new InMemoryWrapper(storage[output]), output)); mock.Setup(fsf => fsf.GetWriter(It.IsAny(), It.IsAny(), It.IsAny())) - .Returns(new ServiceBufferFileStreamWriter(new InMemoryWrapper(), It.IsAny(), 1024, - 1024)); + .Returns((output, chars, xml) => new ServiceBufferFileStreamWriter( + new InMemoryWrapper(storage[output]), output, chars, xml)); return mock.Object; } @@ -121,9 +134,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution { private readonly MemoryStream memoryStream; private bool readingOnly; - private readonly byte[] storage = new byte[8192]; - public InMemoryWrapper() + public InMemoryWrapper(byte[] storage) { memoryStream = new MemoryStream(storage); } @@ -226,62 +238,31 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution #region Service Mocking - public static void GetAutoCompleteTestObjects( - out TextDocumentPosition textDocument, - out ScriptFile scriptFile, - out ConnectionInfo connInfo - ) + public static QueryExecutionService GetPrimedExecutionService(Dictionary[][] data, bool isConnected, bool throwOnRead, WorkspaceService workspaceService) { - textDocument = new TextDocumentPosition - { - TextDocument = new TextDocumentIdentifier {Uri = OwnerUri}, - Position = new Position - { - Line = 0, - Character = 0 - } - }; + // Create a place for the temp "files" to be written + Dictionary storage = new Dictionary(); + // Create the connection factory with the dataset + var factory = CreateTestConnectionInfo(data, throwOnRead).Factory; - connInfo = CreateTestConnectionInfo(null, false); - - var srvConn = GetServerConnection(connInfo); - var metadataProvider = SmoMetadataProvider.CreateConnectedProvider(srvConn); - var binder = BinderProvider.CreateBinder(metadataProvider); - connInfo = Common.CreateTestConnectionInfo(null, false); - - LanguageService.Instance.ScriptParseInfoMap.Add(textDocument.TextDocument.Uri, new ScriptParseInfo()); - - scriptFile = new ScriptFile {ClientFilePath = textDocument.TextDocument.Uri}; + // Mock the connection service + var connectionService = new Mock(); + ConnectionInfo ci = new ConnectionInfo(factory, OwnerUri, StandardConnectionDetails); + ConnectionInfo outValMock; + connectionService + .Setup(service => service.TryFindConnection(It.IsAny(), out outValMock)) + .OutCallback((string owner, out ConnectionInfo connInfo) => connInfo = isConnected ? ci : null) + .Returns(isConnected); + return new QueryExecutionService(connectionService.Object, workspaceService) {BufferFileStreamFactory = GetFileStreamFactory(storage)}; } - public static ServerConnection GetServerConnection(ConnectionInfo connection) - { - string connectionString = ConnectionService.BuildConnectionString(connection.ConnectionDetails); - var sqlConnection = new SqlConnection(connectionString); - return new ServerConnection(sqlConnection); - } - - public static async Task GetPrimedExecutionService(ISqlConnectionFactory factory, bool isConnected, WorkspaceService workspaceService) - { - var connectionService = new ConnectionService(factory); - if (isConnected) - { - await connectionService.Connect(new ConnectParams - { - Connection = StandardConnectionDetails, - OwnerUri = OwnerUri - }); - } - return new QueryExecutionService(connectionService, workspaceService) {BufferFileStreamFactory = GetFileStreamFactory()}; - } - - public static WorkspaceService GetPrimedWorkspaceService() + public static WorkspaceService GetPrimedWorkspaceService(string query) { // Set up file for returning the query var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns(StandardQuery); + fileMock.SetupGet(file => file.Contents).Returns(query); // Set up workspace mock var workspaceService = new Mock>(); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs index 87076204..80ca7fbc 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DataStorage/ServiceBufferFileStreamReaderWriterTests.cs @@ -17,7 +17,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage private static void VerifyReadWrite(int valueLength, T value, Func writeFunc, Func readFunc) { // Setup: Create a mock file stream wrapper - Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper(); + Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper(new byte[8192]); try { // If: @@ -223,7 +223,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage public void StringNullTest() { // Setup: Create a mock file stream wrapper - Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper(); + Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper(new byte[8192]); // If: // ... I write null as a string to the writer @@ -259,7 +259,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.DataStorage public void BytesNullTest() { // Setup: Create a mock file stream wrapper - Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper(); + Common.InMemoryWrapper mockWrapper = new Common.InMemoryWrapper(new byte[8192]); // If: // ... I write null as a string to the writer diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs index 2f103ec0..bc706bb3 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/DisposeTests.cs @@ -47,7 +47,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution .Returns(fileMock.Object); // If: // ... I request a query (doesn't matter what kind) - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri}; var executeRequest = RequestContextMocks.SetupRequestContextMock(null, QueryExecuteCompleteEvent.Type, null, null); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); @@ -59,7 +59,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var disposeRequest = GetQueryDisposeResultContextMock(qdr => { result = qdr; }, null); - queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object).Wait(); + await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object); // Then: // ... I should have seen a successful result @@ -75,11 +75,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution var workspaceService = new Mock>(); // If: // ... I attempt to dispose a query that doesn't exist - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object); + 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); - queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object).Wait(); + await queryService.HandleDisposeRequest(disposeParams, disposeRequest.Object); // Then: // ... I should have gotten an error result @@ -99,8 +99,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) .Returns(fileMock.Object); // ... We need a query service - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, - workspaceService.Object); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService.Object); // If: // ... I execute some bogus query diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs index e76b4ecf..c61237fa 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/ExecuteTests.cs @@ -5,839 +5,17 @@ //#define USE_LIVE_CONNECTION -using System; using System.Data.Common; -using System.Linq; -using System.Threading; using System.Threading.Tasks; using Microsoft.SqlTools.ServiceLayer.Connection; using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol; -using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts; using Microsoft.SqlTools.ServiceLayer.QueryExecution; 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; namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution { public class ExecuteTests { - #region Batch Class Tests - - [Fact] - public void BatchCreationTest() - { - // If I create a new batch... - Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); - - // Then: - // ... The text of the batch should be stored - Assert.NotEmpty(batch.BatchText); - - // ... It should not have executed and no error - Assert.False(batch.HasExecuted, "The query should not have executed."); - Assert.False(batch.HasError, "The batch should not have an error"); - - // ... The results should be empty - Assert.Empty(batch.ResultSets); - Assert.Empty(batch.ResultSummaries); - Assert.Empty(batch.ResultMessages); - - // ... The start line of the batch should be 0 - Assert.Equal(0, batch.Selection.StartLine); - - // ... It's ordinal ID should be what I set it to - Assert.Equal(Common.Ordinal, batch.Id); - } - - [Fact] - public void BatchExecuteNoResultSets() - { - // Setup: Create a callback for batch completion - BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => - { - completionCallbackFired = true; - batchSummaryFromCallback = b.Summary; - return Task.FromResult(0); - }; - - // If I execute a query that should get no result sets - Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); - batch.BatchCompletion += callback; - batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait(); - - // Then: - // ... It should have executed without error - Assert.True(batch.HasExecuted, "The query should have been marked executed."); - Assert.False(batch.HasError, "The batch should not have an error"); - - // ... The results should be empty - Assert.Empty(batch.ResultSets); - Assert.Empty(batch.ResultSummaries); - - // ... The results should not be null - Assert.NotNull(batch.ResultSets); - Assert.NotNull(batch.ResultSummaries); - - // ... There should be a message for how many rows were affected - Assert.Equal(1, batch.ResultMessages.Count()); - - // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); - Assert.NotNull(batchSummaryFromCallback); - } - - [Fact] - public void BatchExecuteOneResultSet() - { - const int resultSets = 1; - ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); - - // Setup: Create a callback for batch completion - BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => - { - completionCallbackFired = true; - batchSummaryFromCallback = b.Summary; - return Task.FromResult(0); - }; - - // If I execute a query that should get one result set - Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); - batch.BatchCompletion += callback; - batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); - - // Then: - // ... It should have executed without error - Assert.True(batch.HasExecuted, "The batch should have been marked executed."); - Assert.False(batch.HasError, "The batch should not have an error"); - - // ... There should be exactly one result set - Assert.Equal(resultSets, batch.ResultSets.Count()); - Assert.Equal(resultSets, batch.ResultSummaries.Length); - - // ... Inside the result set should be with 5 rows - Assert.Equal(Common.StandardRows, batch.ResultSets.First().RowCount); - Assert.Equal(Common.StandardRows, batch.ResultSummaries[0].RowCount); - - // ... Inside the result set should have 5 columns - Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Columns.Length); - Assert.Equal(Common.StandardColumns, batch.ResultSummaries[0].ColumnInfo.Length); - - // ... There should be a message for how many rows were affected - Assert.Equal(resultSets, batch.ResultMessages.Count()); - - // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); - Assert.NotNull(batchSummaryFromCallback); - } - - [Fact] - public void BatchExecuteTwoResultSets() - { - var dataset = new[] { Common.StandardTestData, Common.StandardTestData }; - int resultSets = dataset.Length; - ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false); - - // Setup: Create a callback for batch completion - BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => - { - completionCallbackFired = true; - batchSummaryFromCallback = b.Summary; - return Task.FromResult(0); - }; - - // If I execute a query that should get two result sets - Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); - batch.BatchCompletion += callback; - batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); - - // Then: - // ... It should have executed without error - Assert.True(batch.HasExecuted, "The batch should have been marked executed."); - Assert.False(batch.HasError, "The batch should not have an error"); - - // ... There should be exactly two result sets - Assert.Equal(resultSets, batch.ResultSets.Count()); - - foreach (ResultSet rs in batch.ResultSets) - { - // ... Each result set should have 5 rows - Assert.Equal(Common.StandardRows, rs.RowCount); - - // ... Inside each result set should be 5 columns - Assert.Equal(Common.StandardColumns, rs.Columns.Length); - } - - // ... There should be exactly two result set summaries - Assert.Equal(resultSets, batch.ResultSummaries.Length); - - foreach (ResultSetSummary rs in batch.ResultSummaries) - { - // ... Inside each result summary, there should be 5 rows - Assert.Equal(Common.StandardRows, rs.RowCount); - - // ... Inside each result summary, there should be 5 column definitions - Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length); - } - - // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); - Assert.NotNull(batchSummaryFromCallback); - } - - [Fact] - public void BatchExecuteInvalidQuery() - { - // Setup: Create a callback for batch completion - BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => - { - completionCallbackFired = true; - batchSummaryFromCallback = b.Summary; - return Task.FromResult(0); - }; - - ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); - - // If I execute a batch that is invalid - Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); - batch.BatchCompletion += callback; - batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); - - // Then: - // ... It should have executed with error - Assert.True(batch.HasExecuted); - Assert.True(batch.HasError); - - // ... There should be no result sets - Assert.Empty(batch.ResultSets); - Assert.Empty(batch.ResultSummaries); - - // ... There should be plenty of messages for the error - Assert.NotEmpty(batch.ResultMessages); - - // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); - Assert.NotNull(batchSummaryFromCallback); - } - - [Fact] - public async Task BatchExecuteExecuted() - { - // Setup: Create a callback for batch completion - BatchSummary batchSummaryFromCallback = null; - bool completionCallbackFired = false; - Batch.BatchAsyncEventHandler callback = b => - { - completionCallbackFired = true; - batchSummaryFromCallback = b.Summary; - return Task.FromResult(0); - }; - - ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); - - // If I execute a batch - Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory()); - batch.BatchCompletion += callback; - batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); - - // Then: - // ... It should have executed without error - Assert.True(batch.HasExecuted, "The batch should have been marked executed."); - Assert.False(batch.HasError, "The batch should not have an error"); - - // ... The callback for batch completion should have been fired - Assert.True(completionCallbackFired); - Assert.NotNull(batchSummaryFromCallback); - - // If I execute it again - // Then: - // ... It should throw an invalid operation exception - await Assert.ThrowsAsync(() => - batch.Execute(GetConnection(ci), CancellationToken.None)); - - // ... The data should still be available without error - Assert.False(batch.HasError, "The batch should not be in an error condition"); - Assert.True(batch.HasExecuted, "The batch should still be marked executed."); - Assert.NotEmpty(batch.ResultSets); - Assert.NotEmpty(batch.ResultSummaries); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - public void BatchExecuteNoSql(string query) - { - // If: - // ... I create a batch that has an empty query - // Then: - // ... It should throw an exception - Assert.Throws(() => new Batch(query, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory())); - } - - [Fact] - public void BatchNoBufferFactory() - { - // If: - // ... I create a batch that has no file stream factory - // Then: - // ... It should throw an exception - Assert.Throws(() => new Batch("stuff", Common.SubsectionDocument, Common.Ordinal, null)); - } - - [Fact] - public void BatchInvalidOrdinal() - { - // If: - // ... I create a batch has has an ordinal less than 0 - // Then: - // ... It should throw an exception - Assert.Throws(() => new Batch("stuff", Common.SubsectionDocument, -1, Common.GetFileStreamFactory())); - } - - #endregion - - #region Query Class Tests - - [Fact] - public void QueryExecuteNoQueryText() - { - // If: - // ... I create a query that has a null query text - // Then: - // ... It should throw an exception - Assert.Throws(() => - new Query(null, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), Common.GetFileStreamFactory())); - } - - [Fact] - public void QueryExecuteNoConnectionInfo() - { - // If: - // ... I create a query that has a null connection info - // Then: - // ... It should throw an exception - Assert.Throws(() => new Query("Some Query", null, new QueryExecutionSettings(), Common.GetFileStreamFactory())); - } - - [Fact] - public void QueryExecuteNoSettings() - { - // If: - // ... I create a query that has a null settings - // Then: - // ... It should throw an exception - Assert.Throws(() => - new Query("Some query", Common.CreateTestConnectionInfo(null, false), null, Common.GetFileStreamFactory())); - } - - [Fact] - public void QueryExecuteNoBufferFactory() - { - // If: - // ... I create a query that has a null file stream factory - // Then: - // ... It should throw an exception - Assert.Throws(() => - new Query("Some query", Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(),null)); - } - - [Fact] - public void QueryExecuteSingleBatch() - { - // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => - { - batchCallbacksReceived++; - return Task.CompletedTask; - }; - - // If: - // ... I create a query from a single batch (without separator) - ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); - Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); - query.BatchCompleted += batchCallback; - - // Then: - // ... I should get a single batch to execute that hasn't been executed - Assert.NotEmpty(query.QueryText); - Assert.NotEmpty(query.Batches); - Assert.Equal(1, query.Batches.Length); - Assert.False(query.HasExecuted); - Assert.Throws(() => query.BatchSummaries); - - // If: - // ... I then execute the query - query.Execute(); - query.ExecutionTask.Wait(); - - // Then: - // ... The query should have completed successfully with one batch summary returned - Assert.True(query.HasExecuted); - Assert.NotEmpty(query.BatchSummaries); - Assert.Equal(1, query.BatchSummaries.Length); - - // ... The batch callback should have been called precisely 1 time - Assert.Equal(1, batchCallbacksReceived); - } - - [Fact] - public void QueryExecuteNoOpBatch() - { - // Setup: - // ... Create a callback for batch completion - Batch.BatchAsyncEventHandler batchCallback = summary => - { - throw new Exception("Batch completion callback was called"); - }; - - // If: - // ... I create a query from a single batch that does nothing - ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); - Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); - query.BatchCompleted += batchCallback; - - // Then: - // ... I should get no batches back - Assert.NotEmpty(query.QueryText); - Assert.Empty(query.Batches); - Assert.False(query.HasExecuted); - Assert.Throws(() => query.BatchSummaries); - - // If: - // ... I Then execute the query - query.Execute(); - query.ExecutionTask.Wait(); - - // Then: - // ... The query should have completed successfully with no batch summaries returned - Assert.True(query.HasExecuted); - Assert.Empty(query.BatchSummaries); - } - - [Fact] - public void QueryExecuteMultipleBatches() - { - // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => - { - batchCallbacksReceived++; - return Task.CompletedTask; - }; - - // If: - // ... I create a query from two batches (with separator) - ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); - string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery); - Query query = new Query(queryText, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); - query.BatchCompleted += batchCallback; - - // Then: - // ... I should get back two batches to execute that haven't been executed - Assert.NotEmpty(query.QueryText); - Assert.NotEmpty(query.Batches); - Assert.Equal(2, query.Batches.Length); - Assert.False(query.HasExecuted); - Assert.Throws(() => query.BatchSummaries); - - // If: - // ... I then execute the query - query.Execute(); - query.ExecutionTask.Wait(); - - // Then: - // ... The query should have completed successfully with two batch summaries returned - Assert.True(query.HasExecuted); - Assert.NotEmpty(query.BatchSummaries); - Assert.Equal(2, query.BatchSummaries.Length); - - // ... The batch callback should have been called precisely 2 times - Assert.Equal(2, batchCallbacksReceived); - } - - [Fact] - public void QueryExecuteMultipleBatchesWithNoOp() - { - // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => - { - batchCallbacksReceived++; - return Task.CompletedTask; - }; - - // If: - // ... I create a query from a two batches (with separator) - ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); - string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery); - Query query = new Query(queryText, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); - query.BatchCompleted += batchCallback; - - // Then: - // ... I should get back one batch to execute that hasn't been executed - Assert.NotEmpty(query.QueryText); - Assert.NotEmpty(query.Batches); - Assert.Equal(1, query.Batches.Length); - Assert.False(query.HasExecuted); - Assert.Throws(() => query.BatchSummaries); - - // If: - // .. I then execute the query - query.Execute(); - query.ExecutionTask.Wait(); - - // ... The query should have completed successfully with one batch summary returned - Assert.True(query.HasExecuted); - Assert.NotEmpty(query.BatchSummaries); - Assert.Equal(1, query.BatchSummaries.Length); - - // ... The batch callback should have been called precisely 1 time - Assert.Equal(1, batchCallbacksReceived); - } - - [Fact] - public void QueryExecuteInvalidBatch() - { - // Setup: - // ... Create a callback for batch completion - int batchCallbacksReceived = 0; - Batch.BatchAsyncEventHandler batchCallback = summary => - { - batchCallbacksReceived++; - return Task.CompletedTask; - }; - - // If: - // ... I create a query from an invalid batch - ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); - Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), Common.GetFileStreamFactory()); - query.BatchCompleted += batchCallback; - - // Then: - // ... I should get back a query with one batch not executed - Assert.NotEmpty(query.QueryText); - Assert.NotEmpty(query.Batches); - Assert.Equal(1, query.Batches.Length); - Assert.False(query.HasExecuted); - Assert.Throws(() => query.BatchSummaries); - - // If: - // ... I then execute the query - query.Execute(); - query.ExecutionTask.Wait(); - - // Then: - // ... There should be an error on the batch - Assert.True(query.HasExecuted); - Assert.NotEmpty(query.BatchSummaries); - Assert.Equal(1, query.BatchSummaries.Length); - Assert.True(query.BatchSummaries[0].HasError); - Assert.NotEmpty(query.BatchSummaries[0].Messages); - - // ... The batch callback should have been called once - Assert.Equal(1, batchCallbacksReceived); - } - - #endregion - - #region Service Tests - - [Fact] - public async void QueryExecuteValidNoResultsTest() - { - // Given: - // ... Default settings are stored in the workspace service - WorkspaceService.Instance.CurrentSettings = new SqlToolsSettings(); - - // Set up file for returning the query - var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns(Common.StandardQuery); - // Set up workspace mock - var workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); - // If: - // ... I request to execute a valid query with no results - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); - var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri }; - - QueryExecuteResult result = null; - QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchCompleteParams batchCompleteParams = null; - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); - queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); - - // 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 - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); - Assert.Null(result.Messages); - - Assert.Equal(1, completeParams.BatchSummaries.Length); - Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries); - Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); - - Assert.NotNull(batchCompleteParams); - Assert.Empty(batchCompleteParams.BatchSummary.ResultSetSummaries); - Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); - Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); - - // ... There should be one active query - Assert.Equal(1, queryService.ActiveQueries.Count); - } - - [Fact] - public async void QueryExecuteValidResultsTest() - { - - // 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 to execute a valid query with results - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true, - workspaceService.Object); - var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - - QueryExecuteResult result = null; - QueryExecuteCompleteParams completeParams = null; - QueryExecuteBatchCompleteParams batchCompleteParams = null; - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); - queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); - - // Then: - // ... No errors should have been sent - // ... A successful result should have been sent with messages - // ... A completion event should have been fired with one result - VerifyQueryExecuteCallCount(requestContext, 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); - - Assert.NotNull(batchCompleteParams); - Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); - Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); - Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); - - // ... There should be one active query - Assert.Equal(1, queryService.ActiveQueries.Count); - } - - [Fact] - public async void QueryExecuteUnconnectedUriTest() - { - - var workspaceService = new Mock>(); - // If: - // ... I request to execute a query using a file URI that isn't connected - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), false, workspaceService.Object); - var queryParams = new QueryExecuteParams { OwnerUri = "notConnected", QuerySelection = Common.WholeDocument }; - - object error = null; - var requestContext = RequestContextMocks.Create(null) - .AddErrorHandling(e => error = e); - queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); - - // Then: - // ... An error should have been returned - // ... No result should have been returned - // ... No completion event should have been fired - // ... There should be no active queries - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Once()); - Assert.IsType(error); - Assert.NotEmpty((string)error); - Assert.Empty(queryService.ActiveQueries); - } - - [Fact] - public async void QueryExecuteInProgressTest() - { - - // 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 to execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); - var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - - // Note, we don't care about the results of the first request - var firstRequestContext = RequestContextMocks.Create(null); - await AwaitExecution(queryService, queryParams, firstRequestContext.Object); - - // ... 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 AwaitExecution(queryService, queryParams, secondRequestContext.Object); - - // Then: - // ... An error should have been sent - // ... A result should have not have been sent - // ... No completion event should have been fired - // ... There should only be one active query - VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), Times.Once()); - Assert.IsType(error); - Assert.NotEmpty((string)error); - Assert.Equal(1, queryService.ActiveQueries.Count); - } - - [Fact] - public async void QueryExecuteCompletedTest() - { - - // 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 to execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); - var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - - // Note, we don't care about the results of the first request - var firstRequestContext = RequestContextMocks.Create(null); - - queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait(); - - // ... And then I request another query after waiting for the first to complete - QueryExecuteResult result = null; - QueryExecuteCompleteParams complete = null; - QueryExecuteBatchCompleteParams batchComplete = null; - var secondRequestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); - queryService.HandleExecuteRequest(queryParams, secondRequestContext.Object).Wait(); - - // Then: - // ... No errors should have been sent - // ... A result should have been sent with no errors - // ... There should only be one active query - VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); - Assert.Null(result.Messages); - - Assert.False(complete.BatchSummaries.Any(b => b.HasError)); - Assert.Equal(1, queryService.ActiveQueries.Count); - - Assert.NotNull(batchComplete); - Assert.False(batchComplete.BatchSummary.HasError); - Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); - } - - [Fact] - public async Task QueryExecuteMissingSelectionTest() - { - - // Set up file for returning the query - var fileMock = new Mock(); - fileMock.SetupGet(file => file.Contents).Returns(""); - // Set up workspace mock - var workspaceService = new Mock>(); - workspaceService.Setup(service => service.Workspace.GetFile(It.IsAny())) - .Returns(fileMock.Object); - // If: - // ... I request to execute a query with a missing query string - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); - 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); - - - // Then: - // ... Am error should have been sent - // ... No result should have been sent - // ... No completion event should have been fired - // ... An active query should not have been added - VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Once()); - Assert.NotNull(errorResult); - Assert.IsType(errorResult); - Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys); - - // ... There should not be an active query - Assert.Empty(queryService.ActiveQueries); - } - - [Fact] - public async void QueryExecuteInvalidQueryTest() - { - // 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 to execute a query that is invalid - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, true), true, workspaceService.Object); - var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; - - QueryExecuteResult result = null; - QueryExecuteCompleteParams complete = null; - QueryExecuteBatchCompleteParams batchComplete = null; - var requestContext = RequestContextMocks.Create(qer => result = qer) - .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) - .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); - queryService.HandleExecuteRequest(queryParams, requestContext.Object).Wait(); - - // Then: - // ... No errors should have been sent - // ... A result should have been sent with success (we successfully started the query) - // ... A completion event should have been sent with error - VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); - Assert.Null(result.Messages); - - Assert.Equal(1, complete.BatchSummaries.Length); - Assert.True(complete.BatchSummaries[0].HasError); - Assert.NotEmpty(complete.BatchSummaries[0].Messages); - - Assert.NotNull(batchComplete); - Assert.True(batchComplete.BatchSummary.HasError); - Assert.NotEmpty(batchComplete.BatchSummary.Messages); - Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); - } #if USE_LIVE_CONNECTION [Fact] @@ -866,32 +44,5 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution Assert.NotEmpty(query.BatchSummaries[0].Messages); } #endif - - #endregion - - private static void VerifyQueryExecuteCallCount(Mock> mock, Times sendResultCalls, - Times sendCompletionEventCalls, Times sendBatchCompletionEvent, Times sendErrorCalls) - { - 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.SendError(It.IsAny()), sendErrorCalls); - } - - private static DbConnection GetConnection(ConnectionInfo info) - { - return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails)); - } - - private static async Task AwaitExecution(QueryExecutionService service, QueryExecuteParams qeParams, - RequestContext requestContext) - { - await service.HandleExecuteRequest(qeParams, requestContext); - await service.ActiveQueries[qeParams.OwnerUri].ExecutionTask; - } } } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs new file mode 100644 index 00000000..b36ea6b2 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/BatchTests.cs @@ -0,0 +1,310 @@ +// +// 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.Data.Common; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.QueryExecution; +using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution +{ + public class BatchTests + { + [Fact] + public void BatchCreationTest() + { + // If I create a new batch... + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory(null)); + + // Then: + // ... The text of the batch should be stored + Assert.NotEmpty(batch.BatchText); + + // ... It should not have executed and no error + Assert.False(batch.HasExecuted, "The query should not have executed."); + Assert.False(batch.HasError, "The batch should not have an error"); + + // ... The results should be empty + Assert.Empty(batch.ResultSets); + Assert.Empty(batch.ResultSummaries); + Assert.Empty(batch.ResultMessages); + + // ... The start line of the batch should be 0 + Assert.Equal(0, batch.Selection.StartLine); + + // ... It's ordinal ID should be what I set it to + Assert.Equal(Common.Ordinal, batch.Id); + } + + [Fact] + public void BatchExecuteNoResultSets() + { + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + + // If I execute a query that should get no result sets + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); + batch.BatchCompletion += callback; + batch.Execute(GetConnection(Common.CreateTestConnectionInfo(null, false)), CancellationToken.None).Wait(); + + // Then: + // ... It should have executed without error + Assert.True(batch.HasExecuted, "The query should have been marked executed."); + Assert.False(batch.HasError, "The batch should not have an error"); + + // ... The results should be empty + Assert.Empty(batch.ResultSets); + Assert.Empty(batch.ResultSummaries); + + // ... The results should not be null + Assert.NotNull(batch.ResultSets); + Assert.NotNull(batch.ResultSummaries); + + // ... There should be a message for how many rows were affected + Assert.Equal(1, batch.ResultMessages.Count()); + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); + } + + [Fact] + public void BatchExecuteOneResultSet() + { + const int resultSets = 1; + ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); + + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + + // If I execute a query that should get one result set + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); + batch.BatchCompletion += callback; + batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); + + // Then: + // ... It should have executed without error + Assert.True(batch.HasExecuted, "The batch should have been marked executed."); + Assert.False(batch.HasError, "The batch should not have an error"); + + // ... There should be exactly one result set + Assert.Equal(resultSets, batch.ResultSets.Count()); + Assert.Equal(resultSets, batch.ResultSummaries.Length); + + // ... Inside the result set should be with 5 rows + Assert.Equal(Common.StandardRows, batch.ResultSets.First().RowCount); + Assert.Equal(Common.StandardRows, batch.ResultSummaries[0].RowCount); + + // ... Inside the result set should have 5 columns + Assert.Equal(Common.StandardColumns, batch.ResultSets.First().Columns.Length); + Assert.Equal(Common.StandardColumns, batch.ResultSummaries[0].ColumnInfo.Length); + + // ... There should be a message for how many rows were affected + Assert.Equal(resultSets, batch.ResultMessages.Count()); + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); + } + + [Fact] + public void BatchExecuteTwoResultSets() + { + var dataset = new[] { Common.StandardTestData, Common.StandardTestData }; + int resultSets = dataset.Length; + ConnectionInfo ci = Common.CreateTestConnectionInfo(dataset, false); + + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + + // If I execute a query that should get two result sets + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); + batch.BatchCompletion += callback; + batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); + + // Then: + // ... It should have executed without error + Assert.True(batch.HasExecuted, "The batch should have been marked executed."); + Assert.False(batch.HasError, "The batch should not have an error"); + + // ... There should be exactly two result sets + Assert.Equal(resultSets, batch.ResultSets.Count()); + + foreach (ResultSet rs in batch.ResultSets) + { + // ... Each result set should have 5 rows + Assert.Equal(Common.StandardRows, rs.RowCount); + + // ... Inside each result set should be 5 columns + Assert.Equal(Common.StandardColumns, rs.Columns.Length); + } + + // ... There should be exactly two result set summaries + Assert.Equal(resultSets, batch.ResultSummaries.Length); + + foreach (ResultSetSummary rs in batch.ResultSummaries) + { + // ... Inside each result summary, there should be 5 rows + Assert.Equal(Common.StandardRows, rs.RowCount); + + // ... Inside each result summary, there should be 5 column definitions + Assert.Equal(Common.StandardColumns, rs.ColumnInfo.Length); + } + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); + } + + [Fact] + public void BatchExecuteInvalidQuery() + { + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); + + // If I execute a batch that is invalid + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); + batch.BatchCompletion += callback; + batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); + + // Then: + // ... It should have executed with error + Assert.True(batch.HasExecuted); + Assert.True(batch.HasError); + + // ... There should be no result sets + Assert.Empty(batch.ResultSets); + Assert.Empty(batch.ResultSummaries); + + // ... There should be plenty of messages for the error + Assert.NotEmpty(batch.ResultMessages); + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); + } + + [Fact] + public async Task BatchExecuteExecuted() + { + // Setup: Create a callback for batch completion + BatchSummary batchSummaryFromCallback = null; + bool completionCallbackFired = false; + Batch.BatchAsyncEventHandler callback = b => + { + completionCallbackFired = true; + batchSummaryFromCallback = b.Summary; + return Task.FromResult(0); + }; + + ConnectionInfo ci = Common.CreateTestConnectionInfo(new[] { Common.StandardTestData }, false); + + // If I execute a batch + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Batch batch = new Batch(Common.StandardQuery, Common.SubsectionDocument, Common.Ordinal, fileStreamFactory); + batch.BatchCompletion += callback; + batch.Execute(GetConnection(ci), CancellationToken.None).Wait(); + + // Then: + // ... It should have executed without error + Assert.True(batch.HasExecuted, "The batch should have been marked executed."); + Assert.False(batch.HasError, "The batch should not have an error"); + + // ... The callback for batch completion should have been fired + Assert.True(completionCallbackFired); + Assert.NotNull(batchSummaryFromCallback); + + // If I execute it again + // Then: + // ... It should throw an invalid operation exception + await Assert.ThrowsAsync(() => + batch.Execute(GetConnection(ci), CancellationToken.None)); + + // ... The data should still be available without error + Assert.False(batch.HasError, "The batch should not be in an error condition"); + Assert.True(batch.HasExecuted, "The batch should still be marked executed."); + Assert.NotEmpty(batch.ResultSets); + Assert.NotEmpty(batch.ResultSummaries); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void BatchExecuteNoSql(string query) + { + // If: + // ... I create a batch that has an empty query + // Then: + // ... It should throw an exception + Assert.Throws(() => new Batch(query, Common.SubsectionDocument, Common.Ordinal, Common.GetFileStreamFactory(null))); + } + + [Fact] + public void BatchNoBufferFactory() + { + // If: + // ... I create a batch that has no file stream factory + // Then: + // ... It should throw an exception + Assert.Throws(() => new Batch("stuff", Common.SubsectionDocument, Common.Ordinal, null)); + } + + [Fact] + public void BatchInvalidOrdinal() + { + // If: + // ... I create a batch has has an ordinal less than 0 + // Then: + // ... It should throw an exception + Assert.Throws(() => new Batch("stuff", Common.SubsectionDocument, -1, Common.GetFileStreamFactory(null))); + } + + private static DbConnection GetConnection(ConnectionInfo info) + { + return info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails)); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs new file mode 100644 index 00000000..23aefba6 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/QueryTests.cs @@ -0,0 +1,208 @@ +// +// 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 Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.QueryExecution; +using Microsoft.SqlTools.ServiceLayer.SqlContext; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution +{ + public class QueryTests + { + + [Fact] + public void QueryExecuteNoQueryText() + { + // If: + // ... I create a query that has a null query text + // Then: + // ... It should throw an exception + Assert.Throws(() => + new Query(null, Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), Common.GetFileStreamFactory(null))); + } + + [Fact] + public void QueryExecuteNoConnectionInfo() + { + // If: + // ... I create a query that has a null connection info + // Then: + // ... It should throw an exception + Assert.Throws(() => new Query("Some Query", null, new QueryExecutionSettings(), Common.GetFileStreamFactory(null))); + } + + [Fact] + public void QueryExecuteNoSettings() + { + // If: + // ... I create a query that has a null settings + // Then: + // ... It should throw an exception + Assert.Throws(() => + new Query("Some query", Common.CreateTestConnectionInfo(null, false), null, Common.GetFileStreamFactory(null))); + } + + [Fact] + public void QueryExecuteNoBufferFactory() + { + // If: + // ... I create a query that has a null file stream factory + // Then: + // ... It should throw an exception + Assert.Throws(() => + new Query("Some query", Common.CreateTestConnectionInfo(null, false), new QueryExecutionSettings(), null)); + } + + [Fact] + public void QueryExecuteSingleBatch() + { + // If: + // ... I create a query from a single batch (without separator) + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Query query = new Query(Common.StandardQuery, ci, new QueryExecutionSettings(), fileStreamFactory); + + // Then: + // ... I should get a single batch to execute that hasn't been executed + Assert.NotEmpty(query.QueryText); + Assert.NotEmpty(query.Batches); + Assert.Equal(1, query.Batches.Length); + Assert.False(query.HasExecuted); + Assert.Throws(() => query.BatchSummaries); + + // If: + // ... I then execute the query + query.Execute(); + query.ExecutionTask.Wait(); + + // Then: + // ... The query should have completed successfully with one batch summary returned + Assert.True(query.HasExecuted); + Assert.NotEmpty(query.BatchSummaries); + Assert.Equal(1, query.BatchSummaries.Length); + } + + [Fact] + public void QueryExecuteNoOpBatch() + { + // If: + // ... I create a query from a single batch that does nothing + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Query query = new Query(Common.NoOpQuery, ci, new QueryExecutionSettings(), fileStreamFactory); + + // Then: + // ... I should get no batches back + Assert.NotEmpty(query.QueryText); + Assert.Empty(query.Batches); + Assert.False(query.HasExecuted); + Assert.Throws(() => query.BatchSummaries); + + // If: + // ... I Then execute the query + query.Execute(); + query.ExecutionTask.Wait(); + + // Then: + // ... The query should have completed successfully with no batch summaries returned + Assert.True(query.HasExecuted); + Assert.Empty(query.BatchSummaries); + } + + [Fact] + public void QueryExecuteMultipleBatches() + { + // If: + // ... I create a query from two batches (with separator) + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); + string queryText = string.Format("{0}\r\nGO\r\n{0}", Common.StandardQuery); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory); + + // Then: + // ... I should get back two batches to execute that haven't been executed + Assert.NotEmpty(query.QueryText); + Assert.NotEmpty(query.Batches); + Assert.Equal(2, query.Batches.Length); + Assert.False(query.HasExecuted); + Assert.Throws(() => query.BatchSummaries); + + // If: + // ... I then execute the query + query.Execute(); + query.ExecutionTask.Wait(); + + // Then: + // ... The query should have completed successfully with two batch summaries returned + Assert.True(query.HasExecuted); + Assert.NotEmpty(query.BatchSummaries); + Assert.Equal(2, query.BatchSummaries.Length); + } + + [Fact] + public void QueryExecuteMultipleBatchesWithNoOp() + { + // If: + // ... I create a query from a two batches (with separator) + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, false); + string queryText = string.Format("{0}\r\nGO\r\n{1}", Common.StandardQuery, Common.NoOpQuery); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Query query = new Query(queryText, ci, new QueryExecutionSettings(), fileStreamFactory); + + // Then: + // ... I should get back one batch to execute that hasn't been executed + Assert.NotEmpty(query.QueryText); + Assert.NotEmpty(query.Batches); + Assert.Equal(1, query.Batches.Length); + Assert.False(query.HasExecuted); + Assert.Throws(() => query.BatchSummaries); + + // If: + // .. I then execute the query + query.Execute(); + query.ExecutionTask.Wait(); + + // ... The query should have completed successfully with one batch summary returned + Assert.True(query.HasExecuted); + Assert.NotEmpty(query.BatchSummaries); + Assert.Equal(1, query.BatchSummaries.Length); + } + + [Fact] + public void QueryExecuteInvalidBatch() + { + // If: + // ... I create a query from an invalid batch + ConnectionInfo ci = Common.CreateTestConnectionInfo(null, true); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + Query query = new Query(Common.InvalidQuery, ci, new QueryExecutionSettings(), fileStreamFactory); + + // Then: + // ... I should get back a query with one batch not executed + Assert.NotEmpty(query.QueryText); + Assert.NotEmpty(query.Batches); + Assert.Equal(1, query.Batches.Length); + Assert.False(query.HasExecuted); + Assert.Throws(() => query.BatchSummaries); + + // If: + // ... I then execute the query + query.Execute(); + query.ExecutionTask.Wait(); + + // Then: + // ... There should be an error on the batch + Assert.True(query.HasExecuted); + Assert.NotEmpty(query.BatchSummaries); + Assert.Equal(1, query.BatchSummaries.Length); + Assert.True(query.BatchSummaries[0].HasError); + Assert.NotEmpty(query.BatchSummaries[0].Messages); + } + + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ResultSetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ResultSetTests.cs new file mode 100644 index 00000000..305c8bf8 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ResultSetTests.cs @@ -0,0 +1,175 @@ +// +// 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.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.SqlTools.ServiceLayer.Connection; +using Microsoft.SqlTools.ServiceLayer.QueryExecution; +using Microsoft.SqlTools.ServiceLayer.QueryExecution.Contracts; +using Xunit; + +namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution.Execution +{ + public class ResultSetTests + { + [Fact] + public void ResultCreation() + { + // If: + // ... I create a new result set with a valid db data reader + + DbDataReader mockReader = GetReader(null, false, string.Empty); + ResultSet resultSet = new ResultSet(mockReader, Common.GetFileStreamFactory(new Dictionary())); + + // Then: + // ... There should not be any data read yet + Assert.Null(resultSet.Columns); + Assert.Equal(0, resultSet.RowCount); + } + + [Fact] + public void ResultCreationInvalidReader() + { + // If: + // ... I create a new result set without a reader + // Then: + // ... It should throw an exception + Assert.Throws(() => new ResultSet(null, null)); + + } + + [Fact] + public async Task ReadToEndSuccess() + { + // If: + // ... I create a new resultset with a valid db data reader that has data + // ... and I read it to the end + DbDataReader mockReader = GetReader(new [] {Common.StandardTestData}, false, Common.StandardQuery); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + await resultSet.ReadResultToEnd(CancellationToken.None); + + // Then: + // ... The columns should be set + // ... There should be rows to read back + Assert.NotNull(resultSet.Columns); + Assert.NotEmpty(resultSet.Columns); + Assert.Equal(Common.StandardRows, resultSet.RowCount); + } + + + [Theory] + [InlineData("JSON")] + [InlineData("XML")] + public async Task ReadToEndForXmlJson(string forType) + { + // Setup: + // ... Build a FOR XML or FOR JSON data set + string columnName = string.Format("{0}_F52E2B61-18A1-11d1-B105-00805F49916B", forType); + List> data = new List>(); + for(int i = 0; i < Common.StandardRows; i++) + { + data.Add(new Dictionary { { columnName, "test data"} }); + } + Dictionary[][] dataSets = {data.ToArray()}; + + // If: + // ... I create a new resultset with a valid db data reader that is FOR XML/JSON + // ... and I read it to the end + DbDataReader mockReader = GetReader(dataSets, false, Common.StandardQuery); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + await resultSet.ReadResultToEnd(CancellationToken.None); + + // Then: + // ... There should only be one column + // ... There should only be one row + // ... The result should be marked as complete + Assert.Equal(1, resultSet.Columns.Length); + Assert.Equal(1, resultSet.RowCount); + + // If: + // ... I attempt to read back the results + // Then: + // ... I should only get one row + var subset = await resultSet.GetSubset(0, 10); + Assert.Equal(1, subset.RowCount); + } + + [Fact] + public async Task GetSubsetWithoutExecution() + { + // If: + // ... I create a new result set with a valid db data reader without executing it + DbDataReader mockReader = GetReader(null, false, string.Empty); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + + // Then: + // ... Attempting to read a subset should fail miserably + await Assert.ThrowsAsync(() => resultSet.GetSubset(0, 0)); + } + + [Theory] + [InlineData(-1, 0)] // Too small start row + [InlineData(20, 0)] // Too large start row + [InlineData(0, -1)] // Negative row count + public async Task GetSubsetInvalidParameters(int startRow, int rowCount) + { + // If: + // ... I create a new result set with a valid db data reader + // ... And execute the result + DbDataReader mockReader = GetReader(new[] {Common.StandardTestData}, false, Common.StandardQuery); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + await resultSet.ReadResultToEnd(CancellationToken.None); + + // ... And attempt to get a subset with invalid parameters + // Then: + // ... It should throw an exception for an invalid parameter + await Assert.ThrowsAsync(() => resultSet.GetSubset(startRow, rowCount)); + } + + [Theory] + [InlineData(0, 3)] // Standard scenario, 3 rows should come back + [InlineData(0, 20)] // Asking for too many rows, 5 rows should come back + [InlineData(1, 3)] // Standard scenario from non-zero start + [InlineData(1, 20)] // Asking for too many rows at a non-zero start + public async Task GetSubsetSuccess(int startRow, int rowCount) + { + // If: + // ... I create a new result set with a valid db data reader + // ... And execute the result set + DbDataReader mockReader = GetReader(new[] { Common.StandardTestData }, false, Common.StandardQuery); + var fileStreamFactory = Common.GetFileStreamFactory(new Dictionary()); + ResultSet resultSet = new ResultSet(mockReader, fileStreamFactory); + await resultSet.ReadResultToEnd(CancellationToken.None); + + // ... And attempt to get a subset with valid number of rows + ResultSetSubset subset = await resultSet.GetSubset(startRow, rowCount); + + // Then: + // ... There should be rows in the subset, either the number of rows or the number of + // rows requested or the number of rows in the result set, whichever is lower + long availableRowsFromStart = resultSet.RowCount - startRow; + Assert.Equal(Math.Min(availableRowsFromStart, rowCount), subset.RowCount); + + // ... The rows should have the same number of columns as the resultset + Assert.Equal(resultSet.Columns.Length, subset.Rows[0].Length); + } + + private static DbDataReader GetReader(Dictionary[][] dataSet, bool throwOnRead, string query) + { + var info = Common.CreateTestConnectionInfo(dataSet, throwOnRead); + var connection = info.Factory.CreateSqlConnection(ConnectionService.BuildConnectionString(info.ConnectionDetails)); + var command = connection.CreateCommand(); + command.CommandText = query; + return command.ExecuteReader(); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs new file mode 100644 index 00000000..f3bccf08 --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/Execution/ServiceIntegrationTests.cs @@ -0,0 +1,294 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +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 QueryExecuteValidNoResultsTest() + { + // 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 + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); + var queryParams = new QueryExecuteParams { QuerySelection = Common.WholeDocument, OwnerUri = Common.OwnerUri }; + + QueryExecuteResult result = null; + QueryExecuteCompleteParams completeParams = null; + QueryExecuteBatchCompleteParams batchCompleteParams = null; + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); + await Common.AwaitExecution(queryService, queryParams, requestContext.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 + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); + Assert.Null(result.Messages); + + Assert.Equal(1, completeParams.BatchSummaries.Length); + Assert.Empty(completeParams.BatchSummaries[0].ResultSetSummaries); + Assert.NotEmpty(completeParams.BatchSummaries[0].Messages); + + Assert.NotNull(batchCompleteParams); + Assert.Empty(batchCompleteParams.BatchSummary.ResultSetSummaries); + Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); + Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); + + // ... There should be one active query + Assert.Equal(1, queryService.ActiveQueries.Count); + } + + + [Fact] + public async void QueryExecuteValidResultsTest() + { + // 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 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; + QueryExecuteBatchCompleteParams batchCompleteParams = null; + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, p) => completeParams = p) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchCompleteParams = p); + await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + + // Then: + // ... No errors should have been sent + // ... A successful result should have been sent with messages + // ... A completion event should have been fired with one result + VerifyQueryExecuteCallCount(requestContext, 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); + + Assert.NotNull(batchCompleteParams); + Assert.NotEmpty(batchCompleteParams.BatchSummary.ResultSetSummaries); + Assert.NotEmpty(batchCompleteParams.BatchSummary.Messages); + Assert.Equal(completeParams.OwnerUri, batchCompleteParams.OwnerUri); + + // ... There should be one active query + Assert.Equal(1, queryService.ActiveQueries.Count); + } + + [Fact] + 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 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); + + // Then: + // ... An error should have been returned + // ... No result should have been returned + // ... No completion event should have been fired + // ... There should be no active queries + VerifyQueryExecuteCallCount(requestContext, 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 queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); + var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; + + // Note, we don't care about the results of the first request + var firstRequestContext = RequestContextMocks.Create(null); + await Common.AwaitExecution(queryService, queryParams, firstRequestContext.Object); + + // ... 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); + + // Then: + // ... An error should have been sent + // ... A result should have not have been sent + // ... No completion event should have been fired + // ... There should only be one active query + VerifyQueryExecuteCallCount(secondRequestContext, Times.Never(), Times.AtMostOnce(), Times.AtMostOnce(), 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 queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); + var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; + + // Note, we don't care about the results of the first request + var firstRequestContext = RequestContextMocks.Create(null); + + queryService.HandleExecuteRequest(queryParams, firstRequestContext.Object).Wait(); + + // ... And then I request another query after waiting for the first to complete + QueryExecuteResult result = null; + QueryExecuteCompleteParams complete = null; + QueryExecuteBatchCompleteParams batchComplete = null; + var secondRequestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); + await Common.AwaitExecution(queryService, queryParams, secondRequestContext.Object); + + // Then: + // ... No errors should have been sent + // ... A result should have been sent with no errors + // ... There should only be one active query + VerifyQueryExecuteCallCount(secondRequestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); + Assert.Null(result.Messages); + + Assert.False(complete.BatchSummaries.Any(b => b.HasError)); + Assert.Equal(1, queryService.ActiveQueries.Count); + + Assert.NotNull(batchComplete); + Assert.False(batchComplete.BatchSummary.HasError); + Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); + } + + [Theory] + [InlineData(null)] + public async Task QueryExecuteMissingSelectionTest(SelectionData selection) + { + // Given: + // ... A workspace with a standard query is configured + var workspaceService = Common.GetPrimedWorkspaceService(string.Empty); + + // If: + // ... I request to execute a query with a missing query string + 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); + + + // Then: + // ... Am error should have been sent + // ... No result should have been sent + // ... No completion event should have been fired + // ... An active query should not have been added + VerifyQueryExecuteCallCount(requestContext, Times.Never(), Times.Never(), Times.Never(), Times.Once()); + Assert.NotNull(errorResult); + Assert.IsType(errorResult); + Assert.DoesNotContain(Common.OwnerUri, queryService.ActiveQueries.Keys); + + // ... There should not be an active query + Assert.Empty(queryService.ActiveQueries); + } + + [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 queryService = Common.GetPrimedExecutionService(null, true, true, workspaceService); + var queryParams = new QueryExecuteParams { OwnerUri = Common.OwnerUri, QuerySelection = Common.WholeDocument }; + + QueryExecuteResult result = null; + QueryExecuteCompleteParams complete = null; + QueryExecuteBatchCompleteParams batchComplete = null; + var requestContext = RequestContextMocks.Create(qer => result = qer) + .AddEventHandling(QueryExecuteCompleteEvent.Type, (et, qecp) => complete = qecp) + .AddEventHandling(QueryExecuteBatchCompleteEvent.Type, (et, p) => batchComplete = p); + await Common.AwaitExecution(queryService, queryParams, requestContext.Object); + + // Then: + // ... No errors should have been sent + // ... A result should have been sent with success (we successfully started the query) + // ... A completion event should have been sent with error + VerifyQueryExecuteCallCount(requestContext, Times.Once(), Times.Once(), Times.Once(), Times.Never()); + Assert.Null(result.Messages); + + Assert.Equal(1, complete.BatchSummaries.Length); + Assert.True(complete.BatchSummaries[0].HasError); + Assert.NotEmpty(complete.BatchSummaries[0].Messages); + + Assert.NotNull(batchComplete); + Assert.True(batchComplete.BatchSummary.HasError); + Assert.NotEmpty(batchComplete.BatchSummary.Messages); + Assert.Equal(complete.OwnerUri, batchComplete.OwnerUri); + } + + private static void VerifyQueryExecuteCallCount(Mock> mock, Times sendResultCalls, + Times sendCompletionEventCalls, Times sendBatchCompletionEvent, Times sendErrorCalls) + { + 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.SendError(It.IsAny()), sendErrorCalls); + } + } +} diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs index 470476c2..5c7a063c 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SaveResultsTests.cs @@ -3,16 +3,13 @@ // using System; -using System.Linq; 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.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.Utility; -using Microsoft.SqlTools.ServiceLayer.Workspace; using Moq; using Xunit; @@ -30,11 +27,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public async void SaveResultsAsCsvSuccessTest() { // Execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService()); + var workplaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new [] {Common.StandardTestData}, true, false, workplaceService); 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; + await Common.AwaitExecution(queryService, executeParams, executeRequest.Object); // Request to save the results as csv with correct parameters var saveParams = new SaveResultsAsCsvRequestParams @@ -47,18 +44,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution }; SaveResultRequestResult result = null; var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); - queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsCsvRequest(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 + VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); Assert.Null(result.Messages); Assert.True(File.Exists(saveParams.FilePath)); - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); // Delete temp file after test if (File.Exists(saveParams.FilePath)) @@ -74,11 +69,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public async void SaveResultsAsCsvWithSelectionSuccessTest() { // Execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService()); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new []{Common.StandardTestData}, 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; + await Common.AwaitExecution(queryService, executeParams, executeRequest.Object); // Request to save the results as csv with correct parameters var saveParams = new SaveResultsAsCsvRequestParams @@ -95,7 +90,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution }; SaveResultRequestResult result = null; var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); - queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object); @@ -104,9 +98,9 @@ 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); Assert.True(File.Exists(saveParams.FilePath)); - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); // Delete temp file after test if (File.Exists(saveParams.FilePath)) @@ -120,13 +114,13 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution /// [Fact] public async void SaveResultsAsCsvExceptionTest() - { + { // Execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService()); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new[] {Common.StandardTestData}, 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; + await Common.AwaitExecution(queryService, executeParams, executeRequest.Object); // Request to save the results as csv with incorrect filepath var saveParams = new SaveResultsAsCsvRequestParams @@ -139,17 +133,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution SaveResultRequestError errMessage = null; var saveRequest = GetSaveResultsContextMock( null, err => errMessage = (SaveResultRequestError) err); - queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); // Call save results and wait on the save task await queryService.HandleSaveResultsAsCsvRequest(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 error message - Assert.NotNull(errMessage); VerifySaveResultsCallCount(saveRequest, Times.Never(), Times.Once()); + Assert.NotNull(errMessage); Assert.False(File.Exists(saveParams.FilePath)); } @@ -160,8 +152,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public async void SaveResultsAsCsvQueryNotFoundTest() { // Create a query execution service - var workspaceService = new Mock>(); - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); // Request to save the results as csv with query that is no longer active var saveParams = new SaveResultsAsCsvRequestParams @@ -173,12 +165,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution }; SaveResultRequestResult result = null; var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); - queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object).Wait(); + await queryService.HandleSaveResultsAsCsvRequest(saveParams, saveRequest.Object); // Expect message that save failed + VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); Assert.NotNull(result.Messages); Assert.False(File.Exists(saveParams.FilePath)); - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); } /// @@ -188,11 +180,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public async void SaveResultsAsJsonSuccessTest() { // Execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService()); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new[] {Common.StandardTestData}, 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; + await Common.AwaitExecution(queryService, executeParams, executeRequest.Object); // Request to save the results as json with correct parameters var saveParams = new SaveResultsAsJsonRequestParams @@ -200,19 +192,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution OwnerUri = Common.OwnerUri, ResultSetIndex = 0, BatchIndex = 0, - FilePath = "testwrite_4.json" + FilePath = "testwrite_4.json" }; SaveResultRequestResult result = null; var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); - queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); // 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; - - // Expect to see a file successfully created in filepath and a success message Assert.Null(result.Messages); @@ -233,11 +222,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public async void SaveResultsAsJsonWithSelectionSuccessTest() { // Execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService()); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new[] { Common.StandardTestData }, 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; + await Common.AwaitExecution(queryService, executeParams, executeRequest.Object); // Request to save the results as json with correct parameters var saveParams = new SaveResultsAsJsonRequestParams @@ -253,18 +242,16 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution }; SaveResultRequestResult result = null; var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); - queryService.ActiveQueries[Common.OwnerUri].Batches[0] = Common.GetBasicExecutedBatch(); // 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 + VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); Assert.Null(result.Messages); Assert.True(File.Exists(saveParams.FilePath)); - VerifySaveResultsCallCount(saveRequest, Times.Once(), Times.Never()); // Delete temp file after test if (File.Exists(saveParams.FilePath)) @@ -280,11 +267,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution public async void SaveResultsAsJsonExceptionTest() { // Execute a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService()); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new [] {Common.StandardTestData}, 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; + await Common.AwaitExecution(queryService, executeParams, executeRequest.Object); // Request to save the results as json with incorrect filepath var saveParams = new SaveResultsAsJsonRequestParams @@ -303,8 +290,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution // 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 error message Assert.NotNull(errMessage); @@ -318,10 +304,9 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public async void SaveResultsAsJsonQueryNotFoundTest() { - // Create a query service - var workspaceService = new Mock>(); - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(null, true, false, workspaceService); // Request to save the results as json with query that is no longer active var saveParams = new SaveResultsAsJsonRequestParams @@ -333,7 +318,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution }; SaveResultRequestResult result = null; var saveRequest = GetSaveResultsContextMock(qcr => result = qcr, null); - queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object).Wait(); + await queryService.HandleSaveResultsAsJsonRequest(saveParams, saveRequest.Object); // Expect message that save failed Assert.Equal("Failed to save results, ID not found.", result.Messages); @@ -353,25 +338,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution 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); - } + var requestContext = RequestContextMocks.Create(resultCallback) + .AddErrorHandling(errorCallback); return requestContext; } diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs index 148c2b8a..118d050a 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/QueryExecution/SubsetTests.cs @@ -9,10 +9,7 @@ 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.SqlContext; using Microsoft.SqlTools.ServiceLayer.Test.Utility; -using Microsoft.SqlTools.ServiceLayer.Workspace; -using Microsoft.SqlTools.ServiceLayer.Workspace.Contracts; using Moq; using Xunit; @@ -100,7 +97,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution { // If: // ... I have a batch that hasn't completed execution - Batch b = new Batch(Common.StandardQuery, Common.WholeDocument, Common.Ordinal, Common.GetFileStreamFactory()); + Batch b = new Batch(Common.StandardQuery, Common.WholeDocument, Common.Ordinal, Common.GetFileStreamFactory(null)); Assert.False(b.HasExecuted); // ... And I ask for a subset @@ -135,19 +132,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public async Task SubsetServiceValidTest() { - - // 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 have a query that has results (doesn't matter what) - var queryService = await Common.GetPrimedExecutionService( - Common.CreateMockFactory(new[] {Common.StandardTestData}, false), true, - workspaceService.Object); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new[] {Common.StandardTestData}, true, false, workspaceService); var executeParams = new QueryExecuteParams {QuerySelection = null, OwnerUri = Common.OwnerUri}; var executeRequest = RequestContextMocks.Create(null); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); @@ -171,15 +159,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public async void SubsetServiceMissingQueryTest() { - - var workspaceService = new Mock>(); // If: // ... I ask for a set of results for a file that hasn't executed a query - var queryService = await Common.GetPrimedExecutionService(Common.CreateMockFactory(null, false), true, workspaceService.Object); + 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); - queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object).Wait(); + await queryService.HandleResultSubsetRequest(subsetParams, subsetRequest.Object); // Then: // ... I should have an error result @@ -193,19 +180,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution [Fact] public async void SubsetServiceUnexecutedQueryTest() { - - // 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 have a query that hasn't finished executing (doesn't matter what) - var queryService = await Common.GetPrimedExecutionService( - Common.CreateMockFactory(new[] { Common.StandardTestData }, false), true, - workspaceService.Object); + var workspaceService = Common.GetPrimedWorkspaceService(Common.StandardQuery); + var queryService = Common.GetPrimedExecutionService(new[] { Common.StandardTestData }, true, false, workspaceService); var executeParams = new QueryExecuteParams { QuerySelection = null, OwnerUri = Common.OwnerUri }; var executeRequest = RequestContextMocks.Create(null); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); @@ -232,8 +210,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.QueryExecution { // If: // ... I have a query that doesn't have any result sets - var queryService = await Common.GetPrimedExecutionService( - Common.CreateMockFactory(null, false), true, Common.GetPrimedWorkspaceService()); + 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.Create(null); await queryService.HandleExecuteRequest(executeParams, executeRequest.Object); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/MoqExtensions.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/MoqExtensions.cs new file mode 100644 index 00000000..1da7f48a --- /dev/null +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/MoqExtensions.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +// + +using System.Reflection; +using Moq.Language; +using Moq.Language.Flow; + +namespace Microsoft.SqlTools.ServiceLayer.Test.Utility +{ + public static class MoqExtensions + { + public delegate void OutAction(out TOut outVal); + + public delegate void OutAction(T1 arg1, out TOut outVal); + + public static IReturnsThrows OutCallback( + this ICallback mock, OutAction action) where TMock : class + { + return OutCallbackInternal(mock, action); + } + + public static IReturnsThrows OutCallback( + this ICallback mock, OutAction action) where TMock : class + { + return OutCallbackInternal(mock, action); + } + + private static IReturnsThrows OutCallbackInternal( + ICallback mock, object action) where TMock : class + { + typeof(ICallback).GetTypeInfo() + .Assembly.GetType("Moq.MethodCall") + .GetMethod("SetCallbackWithArguments", + BindingFlags.InvokeMethod | BindingFlags.NonPublic | BindingFlags.Instance) + .Invoke(mock, new[] { action }); + return mock as IReturnsThrows; + + } + } +} \ No newline at end of file diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs index c2765783..b8b93276 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbColumn.cs @@ -8,10 +8,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility { public class TestDbColumn : DbColumn { - public TestDbColumn() + public TestDbColumn(string columnName) { base.IsLong = false; - base.ColumnName = "Test Column"; + base.ColumnName = columnName; base.ColumnSize = 128; base.AllowDBNull = true; base.DataType = typeof(string); diff --git a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbDataReader.cs b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbDataReader.cs index 0330cda0..a9233d0b 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbDataReader.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.Test/Utility/TestDbDataReader.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Common; using System.Linq; -using Moq; namespace Microsoft.SqlTools.ServiceLayer.Test.Utility { @@ -93,7 +92,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Test.Utility List columns = new List(); for (int i = 0; i < ResultSet.Current[0].Count; i++) { - columns.Add(new TestDbColumn()); + columns.Add(new TestDbColumn(ResultSet.Current[0].Keys.ToArray()[i])); } return new ReadOnlyCollection(columns); }