mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 09:59:48 -05:00
This is a reworking of the unit tests to permit us to better test events from the service host. This new Event Flow Validator class allows creating a chain of events that can then be validated after execution of the request. Each event can have its own custom validation logic for verifying that the object sent via the service host is correct. It also allows us to validate that the order of events are correct. The big drawback is that (at this time) the validator cannot support asynchronous events or non-determinant ordering of events. We don't need this for the query execution functionality despite messages being sent asynchronously because async messages aren't sent during unit tests (due to the db message event only being present on SqlDbConnection classes). If the need arises to do async or out of order event validation, then I have some ideas for how we can do that. * Applying the event flow validator to the query execution service integration tests * Undoing changes to events that were included in cherry-picked commit * Cleaning up event flow validation to query execution * Add efv to cancel tests * Adding efv to dispose tests * Adding efv to subset tests * Adding efv to SaveResults tests * Copyright
164 lines
5.5 KiB
C#
164 lines
5.5 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.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol;
|
|
using Microsoft.SqlTools.ServiceLayer.Hosting.Protocol.Contracts;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.Test.Utility
|
|
{
|
|
public class EventFlowValidator<TRequestContext>
|
|
{
|
|
private readonly List<ExpectedEvent> expectedEvents = new List<ExpectedEvent>();
|
|
private readonly List<ReceivedEvent> receivedEvents = new List<ReceivedEvent>();
|
|
private readonly Mock<RequestContext<TRequestContext>> requestContext;
|
|
private bool completed;
|
|
|
|
public EventFlowValidator()
|
|
{
|
|
requestContext = new Mock<RequestContext<TRequestContext>>(MockBehavior.Strict);
|
|
}
|
|
|
|
public RequestContext<TRequestContext> Object
|
|
{
|
|
get { return requestContext.Object; }
|
|
}
|
|
|
|
public EventFlowValidator<TRequestContext> AddEventValidation<TParams>(EventType<TParams> expectedEvent, Action<TParams> paramValidation)
|
|
{
|
|
expectedEvents.Add(new ExpectedEvent
|
|
{
|
|
EventType = EventTypes.Event,
|
|
ParamType = typeof(TParams),
|
|
Validator = paramValidation
|
|
});
|
|
|
|
requestContext.Setup(rc => rc.SendEvent(expectedEvent, It.IsAny<TParams>()))
|
|
.Callback<EventType<TParams>, TParams>((et, p) =>
|
|
{
|
|
receivedEvents.Add(new ReceivedEvent
|
|
{
|
|
EventObject = p,
|
|
EventType = EventTypes.Event
|
|
});
|
|
})
|
|
.Returns(Task.FromResult(0));
|
|
|
|
return this;
|
|
}
|
|
|
|
public EventFlowValidator<TRequestContext> AddResultValidation(Action<TRequestContext> paramValidation)
|
|
{
|
|
// Add the expected event
|
|
expectedEvents.Add(new ExpectedEvent
|
|
{
|
|
EventType = EventTypes.Result,
|
|
ParamType = typeof(TRequestContext),
|
|
Validator = paramValidation
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
public EventFlowValidator<TRequestContext> AddErrorValidation<TParams>(Action<TParams> paramValidation)
|
|
{
|
|
// Add the expected result
|
|
expectedEvents.Add(new ExpectedEvent
|
|
{
|
|
EventType = EventTypes.Error,
|
|
ParamType = typeof(TParams),
|
|
Validator = paramValidation
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
public EventFlowValidator<TRequestContext> Complete()
|
|
{
|
|
// Add general handler for result handling
|
|
requestContext.Setup(rc => rc.SendResult(It.IsAny<TRequestContext>()))
|
|
.Callback<TRequestContext>(r => receivedEvents.Add(new ReceivedEvent
|
|
{
|
|
EventObject = r,
|
|
EventType = EventTypes.Result
|
|
}))
|
|
.Returns(Task.FromResult(0));
|
|
|
|
// Add general handler for error event
|
|
requestContext.AddErrorHandling(o =>
|
|
{
|
|
receivedEvents.Add(new ReceivedEvent
|
|
{
|
|
EventObject = o,
|
|
EventType = EventTypes.Error
|
|
});
|
|
});
|
|
|
|
completed = true;
|
|
return this;
|
|
}
|
|
|
|
public void Validate()
|
|
{
|
|
// Make sure the handlers have been added
|
|
if (!completed)
|
|
{
|
|
throw new Exception("EventFlowValidator must be completed before it can be validated.");
|
|
}
|
|
|
|
// Iterate over the two lists in sync to see if they are the same
|
|
for (int i = 0; i < Math.Max(expectedEvents.Count, receivedEvents.Count); i++)
|
|
{
|
|
// Step 0) Make sure both events exist
|
|
if (i >= expectedEvents.Count)
|
|
{
|
|
throw new Exception($"Unexpected event received: [{receivedEvents[i].EventType}] {receivedEvents[i].EventObject}");
|
|
}
|
|
ExpectedEvent expected = expectedEvents[i];
|
|
|
|
if (i >= receivedEvents.Count)
|
|
{
|
|
throw new Exception($"Expected additional events: [{expectedEvents[i].EventType}] {expectedEvents[i].ParamType}");
|
|
}
|
|
ReceivedEvent received = receivedEvents[i];
|
|
|
|
// Step 1) Make sure the event type matches
|
|
Assert.Equal(expected.EventType, received.EventType);
|
|
|
|
// Step 2) Make sure the param type matches
|
|
Assert.Equal(expected.ParamType, received.EventObject.GetType());
|
|
|
|
// Step 3) Run the validator on the param object
|
|
Assert.NotNull(received.EventObject);
|
|
expected.Validator?.DynamicInvoke(received.EventObject);
|
|
}
|
|
}
|
|
|
|
private enum EventTypes
|
|
{
|
|
Result,
|
|
Error,
|
|
Event
|
|
}
|
|
|
|
private class ExpectedEvent
|
|
{
|
|
public EventTypes EventType { get; set; }
|
|
public Type ParamType { get; set; }
|
|
public Delegate Validator { get; set; }
|
|
}
|
|
|
|
private class ReceivedEvent
|
|
{
|
|
public object EventObject { get; set; }
|
|
public EventTypes EventType { get; set; }
|
|
}
|
|
}
|
|
}
|