mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 01:25:40 -05:00
The main goal of this feature is to enable a command that will 1) Generate a parameterized command for each edit that is in the session 2) Execute that command against the server 3) Update the cached results of the table/view that's being edited with the committed changes (including computed/identity columns) There's some secret sauce in here where I cheated around worrying about gaps in the updated results. This was accomplished by implementing an IComparable for row edit objects that ensures deletes are the *last* actions to occur and that they occur from the bottom of the list up (highest row ID to lowest). Thus, all other actions that are dependent on the row ID are performed first, then the largest row ID is deleted, then next largest, etc. Nevertheless, by the end of a commit the associated ResultSet is still the source of truth. It is expected that the results grid will need updating once changes are committed. Also worth noting, although this pull request supports a "many edits, one commit" approach, it will work just fine for a "one edit, one commit" approach. * WIP * Adding basic commit support. Deletions work! * Nailing down the commit logic, insert commits work! * Updates work! * Fixing bug in DbColumnWrapper IsReadOnly setting * Comments * ResultSet unit tests, fixing issue with seeking in mock writers * Unit tests for RowCreate commands * Unit tests for RowDelete * RowUpdate unit tests * Session and edit base tests * Fixing broken unit tests * Moving constants to constants file * Addressing code review feedback * Fixes from merge issues, string consts * Removing ad-hoc code * fixing as per @abist requests * Fixing a couple more issues
241 lines
9.0 KiB
C#
241 lines
9.0 KiB
C#
//
|
|
// 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.Linq;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.ServiceLayer.EditData;
|
|
using Microsoft.SqlTools.ServiceLayer.EditData.Contracts;
|
|
using Microsoft.SqlTools.ServiceLayer.EditData.UpdateManagement;
|
|
using Microsoft.SqlTools.ServiceLayer.QueryExecution;
|
|
using Microsoft.SqlTools.ServiceLayer.Test.Common;
|
|
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.EditData
|
|
{
|
|
public class ServiceIntegrationTests
|
|
{
|
|
#region EditSession Operation Helper Tests
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" \t\n\r")]
|
|
[InlineData("Does not exist")]
|
|
public async Task NullOrMissingSessionId(string sessionId)
|
|
{
|
|
// Setup:
|
|
// ... Create a edit data service
|
|
var eds = new EditDataService(null, null, null);
|
|
|
|
// ... Create a session params that returns the provided session ID
|
|
var mockParams = new EditCreateRowParams {OwnerUri = sessionId};
|
|
|
|
// If: I ask to perform an action that requires a session
|
|
// Then: I should get an error from it
|
|
var efv = new EventFlowValidator<EditDisposeResult>()
|
|
.AddStandardErrorValidation()
|
|
.Complete();
|
|
await eds.HandleSessionRequest(mockParams, efv.Object, session => null);
|
|
efv.Validate();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task OperationThrows()
|
|
{
|
|
// Setup:
|
|
// ... Create an edit data service with a session
|
|
var eds = new EditDataService(null, null, null);
|
|
eds.ActiveSessions[Common.OwnerUri] = GetDefaultSession();
|
|
|
|
// ... Create a session param that returns the common owner uri
|
|
var mockParams = new EditCreateRowParams { OwnerUri = Common.OwnerUri };
|
|
|
|
// If: I ask to perform an action that requires a session
|
|
// Then: I should get an error from it
|
|
var efv = new EventFlowValidator<EditDisposeResult>()
|
|
.AddStandardErrorValidation()
|
|
.Complete();
|
|
await eds.HandleSessionRequest(mockParams, efv.Object, s => { throw new Exception(); });
|
|
efv.Validate();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Dispose Tests
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" \t\n\r")]
|
|
[InlineData("Does not exist")]
|
|
public async Task DisposeNullOrMissingSessionId(string sessionId)
|
|
{
|
|
// Setup: Create a edit data service
|
|
var eds = new EditDataService(null, null, null);
|
|
|
|
// If: I ask to perform an action that requires a session
|
|
// Then: I should get an error from it
|
|
var efv = new EventFlowValidator<EditDisposeResult>()
|
|
.AddStandardErrorValidation()
|
|
.Complete();
|
|
await eds.HandleDisposeRequest(new EditDisposeParams {OwnerUri = sessionId}, efv.Object);
|
|
efv.Validate();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DisposeSuccess()
|
|
{
|
|
// Setup: Create an edit data service with a session
|
|
var eds = new EditDataService(null, null, null);
|
|
eds.ActiveSessions[Common.OwnerUri] = GetDefaultSession();
|
|
|
|
// If: I ask to dispose of an existing session
|
|
var efv = new EventFlowValidator<EditDisposeResult>()
|
|
.AddResultValidation(Assert.NotNull)
|
|
.Complete();
|
|
await eds.HandleDisposeRequest(new EditDisposeParams {OwnerUri = Common.OwnerUri}, efv.Object);
|
|
|
|
// Then:
|
|
// ... It should have completed successfully
|
|
efv.Validate();
|
|
|
|
// ... And the session should have been removed from the active session list
|
|
Assert.Empty(eds.ActiveSessions);
|
|
}
|
|
|
|
#endregion
|
|
|
|
[Fact]
|
|
public async Task DeleteSuccess()
|
|
{
|
|
// Setup: Create an edit data service with a session
|
|
var eds = new EditDataService(null, null, null);
|
|
eds.ActiveSessions[Constants.OwnerUri] = GetDefaultSession();
|
|
|
|
// If: I validly ask to delete a row
|
|
var efv = new EventFlowValidator<EditDeleteRowResult>()
|
|
.AddResultValidation(Assert.NotNull)
|
|
.Complete();
|
|
await eds.HandleDeleteRowRequest(new EditDeleteRowParams {OwnerUri = Constants.OwnerUri, RowId = 0}, efv.Object);
|
|
|
|
// Then:
|
|
// ... It should be successful
|
|
efv.Validate();
|
|
|
|
// ... There should be a delete in the session
|
|
EditSession s = eds.ActiveSessions[Constants.OwnerUri];
|
|
Assert.True(s.EditCache.Any(e => e.Value is RowDelete));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task CreateSucceeds()
|
|
{
|
|
// Setup: Create an edit data service with a session
|
|
var eds = new EditDataService(null, null, null);
|
|
eds.ActiveSessions[Constants.OwnerUri] = GetDefaultSession();
|
|
|
|
// If: I ask to create a row from a non existant session
|
|
var efv = new EventFlowValidator<EditCreateRowResult>()
|
|
.AddResultValidation(ecrr => { Assert.True(ecrr.NewRowId > 0); })
|
|
.Complete();
|
|
await eds.HandleCreateRowRequest(new EditCreateRowParams { OwnerUri = Constants.OwnerUri }, efv.Object);
|
|
|
|
// Then:
|
|
// ... It should have been successful
|
|
efv.Validate();
|
|
|
|
// ... There should be a create in the session
|
|
EditSession s = eds.ActiveSessions[Constants.OwnerUri];
|
|
Assert.True(s.EditCache.Any(e => e.Value is RowCreate));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task RevertSucceeds()
|
|
{
|
|
// Setup: Create an edit data service with a session that has an pending edit
|
|
var eds = new EditDataService(null, null, null);
|
|
var session = GetDefaultSession();
|
|
session.EditCache[0] = new Mock<RowEditBase>().Object;
|
|
eds.ActiveSessions[Constants.OwnerUri] = session;
|
|
|
|
// If: I ask to revert a row that has a pending edit
|
|
var efv = new EventFlowValidator<EditRevertRowResult>()
|
|
.AddResultValidation(Assert.NotNull)
|
|
.Complete();
|
|
await eds.HandleRevertRowRequest(new EditRevertRowParams { OwnerUri = Constants.OwnerUri, RowId = 0}, efv.Object);
|
|
|
|
// Then:
|
|
// ... It should have succeeded
|
|
efv.Validate();
|
|
|
|
// ... The edit cache should be empty again
|
|
EditSession s = eds.ActiveSessions[Constants.OwnerUri];
|
|
Assert.Empty(s.EditCache);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task UpdateSuccess()
|
|
{
|
|
// Setup: Create an edit data service with a session
|
|
var eds = new EditDataService(null, null, null);
|
|
var session = GetDefaultSession();
|
|
eds.ActiveSessions[Constants.OwnerUri] = session;
|
|
var edit = new Mock<RowEditBase>();
|
|
edit.Setup(e => e.SetCell(It.IsAny<int>(), It.IsAny<string>())).Returns(new EditUpdateCellResult
|
|
{
|
|
NewValue = string.Empty,
|
|
HasCorrections = true,
|
|
IsRevert = false,
|
|
IsNull = false
|
|
});
|
|
session.EditCache[0] = edit.Object;
|
|
|
|
// If: I validly ask to update a cell
|
|
var efv = new EventFlowValidator<EditUpdateCellResult>()
|
|
.AddResultValidation(eucr =>
|
|
{
|
|
Assert.NotNull(eucr);
|
|
Assert.NotNull(eucr.NewValue);
|
|
Assert.False(eucr.IsRevert);
|
|
Assert.False(eucr.IsNull);
|
|
})
|
|
.Complete();
|
|
await eds.HandleUpdateCellRequest(new EditUpdateCellParams { OwnerUri = Constants.OwnerUri, RowId = 0}, efv.Object);
|
|
|
|
// Then:
|
|
// ... It should be successful
|
|
efv.Validate();
|
|
|
|
// ... Set cell should have been called once
|
|
edit.Verify(e => e.SetCell(It.IsAny<int>(), It.IsAny<string>()), Times.Once);
|
|
}
|
|
|
|
private static EditSession GetDefaultSession()
|
|
{
|
|
// ... Create a session with a proper query and metadata
|
|
Query q = QueryExecution.Common.GetBasicExecutedQuery();
|
|
ResultSet rs = q.Batches[0].ResultSets[0];
|
|
IEditTableMetadata etm = Common.GetMetadata(rs.Columns);
|
|
EditSession s = new EditSession(rs, etm);
|
|
return s;
|
|
}
|
|
}
|
|
|
|
public static class EditServiceEventFlowValidatorExtensions
|
|
{
|
|
public static EventFlowValidator<T> AddStandardErrorValidation<T>(this EventFlowValidator<T> evf)
|
|
{
|
|
return evf.AddErrorValidation<string>(p =>
|
|
{
|
|
Assert.NotNull(p);
|
|
Assert.NotEmpty(p);
|
|
});
|
|
}
|
|
}
|
|
}
|