// // 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.Hosting.Protocol; using Microsoft.SqlTools.Hosting.Protocol.Contracts; using Moq; using Xunit; namespace Microsoft.SqlTools.ServiceLayer.UnitTests.Utility { public class EventFlowValidator { private readonly List expectedEvents = new List(); private readonly List receivedEvents = new List(); private readonly Mock> requestContext; private bool completed; public EventFlowValidator() { requestContext = new Mock>(MockBehavior.Strict); } public RequestContext Object { get { return requestContext.Object; } } public EventFlowValidator AddEventValidation(EventType expectedEvent, Action paramValidation) { expectedEvents.Add(new ExpectedEvent { EventType = EventTypes.Event, ParamType = typeof(TParams), Validator = paramValidation }); requestContext.Setup(rc => rc.SendEvent(expectedEvent, It.IsAny())) .Callback, TParams>((et, p) => { receivedEvents.Add(new ReceivedEvent { EventObject = p, EventType = EventTypes.Event }); }) .Returns(Task.FromResult(0)); return this; } public EventFlowValidator AddResultValidation(Action paramValidation) { // Add the expected event expectedEvents.Add(new ExpectedEvent { EventType = EventTypes.Result, ParamType = typeof(TRequestContext), Validator = paramValidation }); return this; } public EventFlowValidator AddErrorValidation(Action paramValidation) { // Add the expected result expectedEvents.Add(new ExpectedEvent { EventType = EventTypes.Error, ParamType = typeof(TParams), Validator = paramValidation }); return this; } public EventFlowValidator Complete() { // Add general handler for result handling requestContext.Setup(rc => rc.SendResult(It.IsAny())) .Callback(r => receivedEvents.Add(new ReceivedEvent { EventObject = r, EventType = EventTypes.Result })) .Returns(Task.FromResult(0)); // Add general handler for error event requestContext.AddErrorHandling(o => { receivedEvents.Add(new ReceivedEvent { EventObject = o, EventType = EventTypes.Error }); }); completed = true; return this; } public void Validate() { // Make sure the handlers have been added if (!completed) { throw new Exception("EventFlowValidator must be completed before it can be validated."); } // Iterate over the two lists in sync to see if they are the same for (int i = 0; i < Math.Max(expectedEvents.Count, receivedEvents.Count); i++) { // Step 0) Make sure both events exist if (i >= expectedEvents.Count) { throw new Exception($"Unexpected event received: [{receivedEvents[i].EventType}] {receivedEvents[i].EventObject}"); } ExpectedEvent expected = expectedEvents[i]; if (i >= receivedEvents.Count) { throw new Exception($"Expected additional events: [{expectedEvents[i].EventType}] {expectedEvents[i].ParamType}"); } ReceivedEvent received = receivedEvents[i]; // Step 1) Make sure the event type matches Assert.Equal(expected.EventType, received.EventType); // Step 2) Make sure the param type matches Assert.Equal(expected.ParamType, received.EventObject.GetType()); // Step 3) Run the validator on the param object Assert.NotNull(received.EventObject); expected.Validator?.DynamicInvoke(received.EventObject); } } private enum EventTypes { Result, Error, Event } private class ExpectedEvent { public EventTypes EventType { get; set; } public Type ParamType { get; set; } public Delegate Validator { get; set; } } private class ReceivedEvent { public object EventObject { get; set; } public EventTypes EventType { get; set; } } } }