mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-01-14 09:59:48 -05:00
This change ensures that when calling `requestContext.SendError` you are only able to supply parameters that match the language service beta protocol expected Error object. In other words, you have to provide an error message and optionally and error code. # **BREAKING API CHANGES** This will break displaying errors in Microsoft/vscode-mssql. I will be making changes to properly handle the error object shortly. * Adding contract for returning Error objects as per LanguageService "protocol" * Fixes throughout codebase to send only error message in error cases Cleanup of CredentialServiceTest unit test class Adding standard error handling for event flow validator * Adding optional data field as per protocol spec https://github.com/Microsoft/language-server-protocol/blob/master/protocol.md * Adding optional validation for error objects
203 lines
6.9 KiB
C#
203 lines
6.9 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.Hosting.Contracts;
|
|
using Microsoft.SqlTools.Hosting.Protocol;
|
|
using Microsoft.SqlTools.Hosting.Protocol.Contracts;
|
|
using Moq;
|
|
using Xunit;
|
|
|
|
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.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 => 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> AddCompleteErrorValidation<TErrorObj>(Action<string, int> paramValidation,
|
|
Action<TErrorObj> dataValidation)
|
|
{
|
|
// Put together a validator that checks for null and adds the provided validators
|
|
Action<Error> validator = e =>
|
|
{
|
|
Assert.NotNull(e);
|
|
paramValidation(e.Message, e.Code);
|
|
|
|
Assert.IsType<TErrorObj>(e.Data);
|
|
dataValidation((TErrorObj) e.Data);
|
|
};
|
|
|
|
// Add the expected error
|
|
expectedEvents.Add(new ExpectedEvent
|
|
{
|
|
EventType = EventTypes.Error,
|
|
ParamType = typeof(Error),
|
|
Validator = validator
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
public EventFlowValidator<TRequestContext> AddSimpleErrorValidation(Action<string, int> paramValidation)
|
|
{
|
|
// Put together a validator that ensures a null data
|
|
Action<Error> validator = e =>
|
|
{
|
|
Assert.NotNull(e);
|
|
Assert.Null(e.Data);
|
|
paramValidation(e.Message, e.Code);
|
|
};
|
|
|
|
// Add the expected result
|
|
expectedEvents.Add(new ExpectedEvent
|
|
{
|
|
EventType = EventTypes.Error,
|
|
ParamType = typeof(Error),
|
|
Validator = validator
|
|
});
|
|
|
|
return this;
|
|
}
|
|
|
|
public EventFlowValidator<TRequestContext> AddStandardErrorValidation()
|
|
{
|
|
// Add an error validator that just ensures a non-empty error message and null data obj
|
|
return AddSimpleErrorValidation((msg, code) =>
|
|
{
|
|
Assert.NotEmpty(msg);
|
|
});
|
|
}
|
|
|
|
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((msg, code, obj) =>
|
|
{
|
|
receivedEvents.Add(new ReceivedEvent
|
|
{
|
|
EventObject = new Error {Message = msg, Code = code},
|
|
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; }
|
|
}
|
|
}
|
|
}
|