Object Explorer Service (#311)

* moving OE service from an old branch
This commit is contained in:
Leila Lali
2017-04-11 15:50:20 -07:00
committed by GitHub
parent 90861b7d9e
commit d903ba56a9
58 changed files with 15013 additions and 1057 deletions

View File

@@ -0,0 +1,369 @@
//
// 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.SqlClient;
using System.Globalization;
using Microsoft.SqlServer.Management.Common;
using Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel;
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
using Moq;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
{
/// <summary>
/// Tests covering basic operation of Node based classes
/// </summary>
public class NodeTests : ObjectExplorerTestBase
{
private string defaultOwnerUri = "objectexplorer://myserver";
private ServerInfo defaultServerInfo;
private ConnectionDetails defaultConnectionDetails;
private ConnectionCompleteParams defaultConnParams;
private string fakeConnectionString = "Data Source=server;Initial Catalog=database;Integrated Security=False;User Id=user";
public NodeTests()
{
defaultServerInfo = TestObjects.GetTestServerInfo();
defaultConnectionDetails = new ConnectionDetails()
{
DatabaseName = "master",
ServerName = "localhost",
UserName = "serverAdmin",
Password = "..."
};
defaultConnParams = new ConnectionCompleteParams()
{
ServerInfo = defaultServerInfo,
ConnectionSummary = defaultConnectionDetails,
OwnerUri = defaultOwnerUri
};
// TODO can all tests use the standard service provider?
ServiceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider();
}
[Fact]
public void ServerNodeConstructorValidatesFields()
{
Assert.Throws<ArgumentNullException>(() => new ServerNode(null, ServiceProvider));
Assert.Throws<ArgumentNullException>(() => new ServerNode(defaultConnParams, null));
}
[Fact]
public void ServerNodeConstructorShouldSetValuesCorrectly()
{
// Given a server node with valid inputs
ServerNode node = new ServerNode(defaultConnParams, ServiceProvider);
// Then expect all fields set correctly
Assert.False(node.IsAlwaysLeaf, "Server node should never be a leaf");
Assert.Equal(defaultConnectionDetails.ServerName, node.NodeValue);
string expectedLabel = defaultConnectionDetails.ServerName + " (SQL Server " + defaultServerInfo.ServerVersion + " - "
+ defaultConnectionDetails.UserName + ")";
Assert.Equal(expectedLabel, node.Label);
Assert.Equal(NodeTypes.ServerInstance.ToString(), node.NodeType);
string[] nodePath = node.GetNodePath().Split(TreeNode.PathPartSeperator);
Assert.Equal(1, nodePath.Length);
Assert.Equal(defaultConnectionDetails.ServerName, nodePath[0]);
}
[Fact]
public void ServerNodeLabelShouldIgnoreUserNameIfEmptyOrNull()
{
// Given no username set
ConnectionSummary integratedAuthSummary = new ConnectionSummary()
{
DatabaseName = defaultConnectionDetails.DatabaseName,
ServerName = defaultConnectionDetails.ServerName,
UserName = null
};
ConnectionCompleteParams connParams = new ConnectionCompleteParams()
{
ConnectionSummary = integratedAuthSummary,
ServerInfo = defaultServerInfo,
OwnerUri = defaultOwnerUri
};
// When querying label
string label = new ServerNode(connParams, ServiceProvider).Label;
// Then only server name and version shown
string expectedLabel = defaultConnectionDetails.ServerName + " (SQL Server " + defaultServerInfo.ServerVersion + ")";
Assert.Equal(expectedLabel, label);
}
[Fact]
public void ServerNodeConstructorShouldShowDbNameForCloud()
{
defaultServerInfo.IsCloud = true;
// Given a server node for a cloud DB, with master name
ServerNode node = new ServerNode(defaultConnParams, ServiceProvider);
// Then expect label to not include db name
string expectedLabel = defaultConnectionDetails.ServerName + " (SQL Server " + defaultServerInfo.ServerVersion + " - "
+ defaultConnectionDetails.UserName + ")";
Assert.Equal(expectedLabel, node.Label);
// But given a server node for a cloud DB that's not master
defaultConnectionDetails.DatabaseName = "NotMaster";
node = new ServerNode(defaultConnParams, ServiceProvider);
// Then expect label to include db name
expectedLabel = defaultConnectionDetails.ServerName + " (SQL Server " + defaultServerInfo.ServerVersion + " - "
+ defaultConnectionDetails.UserName + ", " + defaultConnectionDetails.DatabaseName + ")";
Assert.Equal(expectedLabel, node.Label);
}
[Fact]
public void ToNodeInfoIncludeAllFields()
{
// Given a server connection
ServerNode node = new ServerNode(defaultConnParams, ServiceProvider);
// When converting to NodeInfo
NodeInfo info = node.ToNodeInfo();
// Then all fields should match
Assert.Equal(node.IsAlwaysLeaf, info.IsLeaf);
Assert.Equal(node.Label, info.Label);
Assert.Equal(node.NodeType, info.NodeType);
string[] nodePath = node.GetNodePath().Split(TreeNode.PathPartSeperator);
string[] nodeInfoPathParts = info.NodePath.Split(TreeNode.PathPartSeperator);
Assert.Equal(nodePath.Length, nodeInfoPathParts.Length);
for (int i = 0; i < nodePath.Length; i++)
{
Assert.Equal(nodePath[i], nodeInfoPathParts[i]);
}
}
[Fact]
public void AddChildShouldSetParent()
{
TreeNode parent = new TreeNode("parent");
TreeNode child = new TreeNode("child");
Assert.Null(child.Parent);
parent.AddChild(child);
Assert.Equal(parent, child.Parent);
}
[Fact]
public void GetChildrenShouldReturnReadonlyList()
{
TreeNode node = new TreeNode("parent");
IList<TreeNode> children = node.GetChildren();
Assert.Throws<NotSupportedException>(() => children.Add(new TreeNode("child")));
}
[Fact]
public void GetChildrenShouldReturnAddedNodesInOrder()
{
TreeNode parent = new TreeNode("parent");
TreeNode[] expectedKids = new TreeNode[] { new TreeNode("1"), new TreeNode("2") };
foreach (TreeNode child in expectedKids)
{
parent.AddChild(child);
}
IList<TreeNode> children = parent.GetChildren();
Assert.Equal(expectedKids.Length, children.Count);
for (int i = 0; i < expectedKids.Length; i++)
{
Assert.Equal(expectedKids[i], children[i]);
}
}
public void MultiLevelTreeShouldFormatPath()
{
TreeNode root = new TreeNode("root");
Assert.Equal("/root" , root.GetNodePath());
TreeNode level1Child1 = new TreeNode("L1C1");
TreeNode level1Child2 = new TreeNode("L1C2");
root.AddChild(level1Child1);
root.AddChild(level1Child2);
Assert.Equal("/root/L1C1" , level1Child1.GetNodePath());
Assert.Equal("/root/L1C2", level1Child2.GetNodePath());
TreeNode level2Child1 = new TreeNode("L2C2");
level1Child1.AddChild(level2Child1);
Assert.Equal("/root/L1C1/L2C2", level2Child1.GetNodePath());
}
[Fact]
public void ServerNodeContextShouldIncludeServer()
{
// given a successful Server creation
SetupAndRegisterTestConnectionService();
Server smoServer = new Server(new ServerConnection(new SqlConnection(fakeConnectionString)));
ServerNode node = SetupServerNodeWithServer(smoServer);
// When I get the context for a ServerNode
var context = node.GetContextAs<SmoQueryContext>();
// Then I expect it to contain the server I created
Assert.NotNull(context);
Assert.Equal(smoServer, context.Server);
// And the server should be the parent
Assert.Equal(smoServer, context.Parent);
Assert.Null(context.Database);
}
[Fact]
public void ServerNodeContextShouldSetErrorMessageIfSqlConnectionIsNull()
{
// given a connectionInfo with no SqlConnection to use for queries
ConnectionService connService = SetupAndRegisterTestConnectionService();
connService.OwnerToConnectionMap.Remove(defaultOwnerUri);
Server smoServer = new Server(new ServerConnection(new SqlConnection(fakeConnectionString)));
ServerNode node = SetupServerNodeWithServer(smoServer);
// When I get the context for a ServerNode
var context = node.GetContextAs<SmoQueryContext>();
// Then I expect it to be in an error state
Assert.Null(context);
Assert.Equal(
string.Format(CultureInfo.CurrentCulture, SR.ServerNodeConnectionError, defaultConnectionDetails.ServerName),
node.ErrorStateMessage);
}
[Fact]
public void ServerNodeContextShouldSetErrorMessageIfConnFailureExceptionThrown()
{
// given a connectionInfo with no SqlConnection to use for queries
SetupAndRegisterTestConnectionService();
Server smoServer = new Server(new ServerConnection(new SqlConnection(fakeConnectionString)));
string expectedMsg = "ConnFailed!";
ServerNode node = SetupServerNodeWithExceptionCreator(new ConnectionFailureException(expectedMsg));
// When I get the context for a ServerNode
var context = node.GetContextAs<SmoQueryContext>();
// Then I expect it to be in an error state
Assert.Null(context);
Assert.Equal(
string.Format(CultureInfo.CurrentCulture, SR.TreeNodeError, expectedMsg),
node.ErrorStateMessage);
}
[Fact]
public void ServerNodeContextShouldSetErrorMessageIfExceptionThrown()
{
// given a connectionInfo with no SqlConnection to use for queries
SetupAndRegisterTestConnectionService();
Server smoServer = new Server(new ServerConnection(new SqlConnection(fakeConnectionString)));
string expectedMsg = "Failed!";
ServerNode node = SetupServerNodeWithExceptionCreator(new Exception(expectedMsg));
// When I get the context for a ServerNode
var context = node.GetContextAs<SmoQueryContext>();
// Then I expect it to be in an error state
Assert.Null(context);
Assert.Equal(
string.Format(CultureInfo.CurrentCulture, SR.TreeNodeError, expectedMsg),
node.ErrorStateMessage);
}
private ConnectionService SetupAndRegisterTestConnectionService()
{
ConnectionService connService = TestObjects.GetTestConnectionService();
ConnectionInfo connectionInfo = new ConnectionInfo(TestObjects.GetTestSqlConnectionFactory(),
defaultOwnerUri, defaultConnectionDetails);
connectionInfo.AddConnection("Default", new SqlConnection());
connService.OwnerToConnectionMap.Add(defaultOwnerUri, connectionInfo);
ServiceProvider.RegisterSingleService(connService);
return connService;
}
private ServerNode SetupServerNodeWithServer(Server smoServer)
{
Mock<SmoServerCreator> creator = new Mock<SmoServerCreator>();
creator.Setup(c => c.Create(It.IsAny<SqlConnection>()))
.Returns(() => smoServer);
ServerNode node = SetupServerNodeWithCreator(creator.Object);
return node;
}
private ServerNode SetupServerNodeWithExceptionCreator(Exception ex)
{
Mock<SmoServerCreator> creator = new Mock<SmoServerCreator>();
creator.Setup(c => c.Create(It.IsAny<SqlConnection>()))
.Throws(ex);
ServerNode node = SetupServerNodeWithCreator(creator.Object);
return node;
}
private ServerNode SetupServerNodeWithCreator(SmoServerCreator creator)
{
ServerNode node = new ServerNode(defaultConnParams, ServiceProvider);
node.ServerCreator = creator;
return node;
}
[Fact]
public void ServerNodeChildrenShouldIncludeFoldersAndDatabases()
{
// Given a server with 1 database
SetupAndRegisterTestConnectionService();
ServiceProvider.RegisterSingleService(new ObjectExplorerService());
string dbName = "DB1";
Mock<NamedSmoObject> smoObjectMock = new Mock<NamedSmoObject>();
smoObjectMock.SetupGet(s => s.Name).Returns(dbName);
Mock<SqlDatabaseQuerier> querierMock = new Mock<SqlDatabaseQuerier>();
querierMock.Setup(q => q.Query(It.IsAny<SmoQueryContext>()))
.Returns(smoObjectMock.Object.SingleItemAsEnumerable());
ServiceProvider.Register<SmoQuerier>(() => new[] { querierMock.Object });
Server smoServer = new Server(new ServerConnection(new SqlConnection(fakeConnectionString)));
ServerNode node = SetupServerNodeWithServer(smoServer);
// When I populate its children
IList<TreeNode> children = node.Expand();
// Then I expect it to contain server-level folders
Assert.Equal(3, children.Count);
VerifyTreeNode<FolderNode>(children[0], "Folder", SR.SchemaHierarchy_Databases);
VerifyTreeNode<FolderNode>(children[1], "Folder", SR.SchemaHierarchy_Security);
VerifyTreeNode<FolderNode>(children[2], "Folder", SR.SchemaHierarchy_ServerObjects);
// And the database is contained under it
TreeNode databases = children[0];
IList<TreeNode> dbChildren = databases.Expand();
Assert.Equal(2, dbChildren.Count);
Assert.Equal("System Databases", dbChildren[0].NodeValue);
TreeNode dbNode = dbChildren[1];
Assert.Equal(dbName, dbNode.NodeValue);
Assert.Equal(dbName, dbNode.Label);
Assert.False(dbNode.IsAlwaysLeaf);
// Note: would like to verify Database in the context, but cannot since it's a Sealed class and isn't easily mockable
}
private void VerifyTreeNode<T>(TreeNode node, string nodeType, string folderValue)
where T : TreeNode
{
T nodeAsT = node as T;
Assert.NotNull(nodeAsT);
Assert.Equal(nodeType, nodeAsT.NodeType);
Assert.Equal(folderValue, nodeAsT.NodeValue);
}
}
}

View File

@@ -0,0 +1,151 @@
//
// 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.Threading.Tasks;
using Microsoft.SqlTools.Hosting.Protocol;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.Connection.Contracts;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Contracts;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.Nodes;
using Microsoft.SqlTools.ServiceLayer.Test.Common;
using Microsoft.SqlTools.ServiceLayer.UnitTests.Utility;
using Moq;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
{
public class ObjectExplorerServiceTests : ObjectExplorerTestBase
{
private ObjectExplorerService service;
private Mock<ConnectionService> connectionServiceMock;
private Mock<IProtocolEndpoint> serviceHostMock;
public ObjectExplorerServiceTests()
{
connectionServiceMock = new Mock<ConnectionService>();
serviceHostMock = new Mock<IProtocolEndpoint>();
service = CreateOEService(connectionServiceMock.Object);
service.InitializeService(serviceHostMock.Object);
}
[Fact]
public async Task CreateSessionRequestErrorsIfConnectionDetailsIsNull()
{
object errorResponse = null;
var contextMock = RequestContextMocks.Create<CreateSessionResponse>(null)
.AddErrorHandling((errorMessage, errorCode, obj) => errorResponse = errorMessage);
await service.HandleCreateSessionRequest(null, contextMock.Object);
VerifyErrorSent(contextMock);
Assert.True(((string)errorResponse).Contains("ArgumentNullException"));
}
[Fact]
public async Task CreateSessionRequestReturnsFalseOnConnectionFailure()
{
// Given the connection service fails to connect
ConnectionDetails details = TestObjects.GetTestConnectionDetails();
ConnectionCompleteParams completeParams = null;
serviceHostMock.AddEventHandling(ConnectionCompleteNotification.Type, (et, p) => completeParams = p);
string expectedExceptionText = "Error!!!";
connectionServiceMock.Setup(c => c.Connect(It.IsAny<ConnectParams>()))
.Throws(new Exception(expectedExceptionText));
// when creating a new session
// then expect the create session request to return false
await RunAndVerify<CreateSessionResponse>(
test: (requestContext) => service.HandleCreateSessionRequest(details, requestContext),
verify: (actual =>
{
Assert.False(actual.Success);
Assert.Null(actual.SessionId);
Assert.Null(actual.RootNode);
}));
// And expect error notification to be sent
serviceHostMock.Verify(x => x.SendEvent(ConnectionCompleteNotification.Type, It.IsAny<ConnectionCompleteParams>()), Times.Once());
Assert.NotNull(completeParams);
Assert.True(completeParams.Messages.Contains(expectedExceptionText));
}
[Fact]
public async Task CreateSessionRequestReturnsSuccessAndNodeInfo()
{
// Given the connection service fails to connect
ConnectionDetails details = TestObjects.GetTestConnectionDetails();
serviceHostMock.AddEventHandling(ConnectionCompleteNotification.Type, null);
connectionServiceMock.Setup(c => c.Connect(It.IsAny<ConnectParams>()))
.Returns((ConnectParams connectParams) => Task.FromResult(GetCompleteParamsForConnection(connectParams.OwnerUri, details)));
// when creating a new session
// then expect the create session request to return false
await RunAndVerify<CreateSessionResponse>(
test: (requestContext) => service.HandleCreateSessionRequest(details, requestContext),
verify: (actual =>
{
Assert.True(actual.Success);
Assert.NotNull(actual.SessionId);
VerifyServerNode(actual.RootNode, details);
}));
// And expect no error notification to be sent
serviceHostMock.Verify(x => x.SendEvent(ConnectionCompleteNotification.Type,
It.IsAny<ConnectionCompleteParams>()), Times.Never());
}
private void VerifyServerNode(NodeInfo serverNode, ConnectionDetails details)
{
Assert.NotNull(serverNode);
Assert.Equal(NodeTypes.ServerInstance.ToString(), serverNode.NodeType);
string[] pathParts = serverNode.NodePath.Split(TreeNode.PathPartSeperator);
Assert.Equal(1, pathParts.Length);
Assert.Equal(details.ServerName, pathParts[0]);
Assert.True(serverNode.Label.Contains(details.ServerName));
Assert.False(serverNode.IsLeaf);
}
private static ConnectionCompleteParams GetCompleteParamsForConnection(string uri, ConnectionDetails details)
{
return new ConnectionCompleteParams()
{
OwnerUri = uri,
ConnectionSummary = new ConnectionSummary()
{
ServerName = details.ServerName,
DatabaseName = details.DatabaseName,
UserName = details.UserName
},
ServerInfo = TestObjects.GetTestServerInfo()
};
}
private async Task RunAndVerify<T>(Func<RequestContext<T>, Task> test, Action<T> verify)
{
T result = default(T);
var contextMock = RequestContextMocks.Create<T>(r => result = r).AddErrorHandling(null);
await test(contextMock.Object);
VerifyResult(contextMock, verify, result);
}
private void VerifyResult<T>(Mock<RequestContext<T>> contextMock, Action<T> verify, T actual)
{
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Once);
contextMock.Verify(c => c.SendError(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<object>()), Times.Never);
verify(actual);
}
private void VerifyErrorSent<T>(Mock<RequestContext<T>> contextMock)
{
contextMock.Verify(c => c.SendResult(It.IsAny<T>()), Times.Never);
contextMock.Verify(c => c.SendError(It.IsAny<string>(), It.IsAny<int>(), It.IsAny<object>()), Times.Once);
}
}
}

View File

@@ -0,0 +1,45 @@
//
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
//
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.ServiceLayer.Connection;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
{
// Base class providing common test functionality for OE tests
public abstract class ObjectExplorerTestBase
{
protected RegisteredServiceProvider ServiceProvider
{
get;
set;
}
protected RegisteredServiceProvider CreateServiceProviderWithMinServices()
{
return CreateProvider()
.RegisterSingleService(new ConnectionService())
.RegisterSingleService(new ObjectExplorerService());
}
protected RegisteredServiceProvider CreateProvider()
{
ServiceProvider = new RegisteredServiceProvider();
return ServiceProvider;
}
protected ObjectExplorerService CreateOEService(ConnectionService connService)
{
CreateProvider()
.RegisterSingleService(connService)
.RegisterSingleService(new ObjectExplorerService());
// Create the service using the service provider, which will initialize dependencies
return ServiceProvider.GetService<ObjectExplorerService>();
}
}
}

View File

@@ -0,0 +1,65 @@
//
// 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 Microsoft.SqlServer.Management.Smo;
using Microsoft.SqlTools.Extensibility;
using Microsoft.SqlTools.ServiceLayer.ObjectExplorer.SmoModel;
using Xunit;
namespace Microsoft.SqlTools.ServiceLayer.UnitTests.ObjectExplorer
{
public class SmoQueryModelTests
{
[Fact]
public void ShouldFindDatabaseQuerierFromRealPath()
{
// Given the extension type loader is set to find SmoCollectionQuerier objects
IMultiServiceProvider serviceProvider = ExtensionServiceProvider.CreateDefaultServiceProvider();
// When I request a database compatible querier
SmoQuerier querier = serviceProvider.GetService<SmoQuerier>(q => q.SupportedObjectTypes.Contains(typeof(Database)));
// Then I expect to get back the SqlDatabaseQuerier
Assert.NotNull(querier);
Assert.Equal(typeof(SqlDatabaseQuerier), querier.GetType());
// And I expect the service provider to have been set by the extension code
Assert.NotNull(querier.ServiceProvider);
}
[Fact]
public void ShouldFindQuerierIfInExtensionList()
{
VerifyQuerierLookup(typeof(Table), typeof(SqlTableQuerier), expectExists: true);
}
[Fact]
public void ShouldNotFindQuerierIfNotInExtensionList()
{
VerifyQuerierLookup(typeof(Database), null, expectExists: false);
}
private static void VerifyQuerierLookup(Type smoType, Type querierType, bool expectExists)
{
ExtensionServiceProvider serviceProvider = ExtensionServiceProvider.Create(new Type[] {
typeof(SqlTableQuerier),
typeof(SqlLinkedServerQuerier)
});
SmoQuerier querier = serviceProvider.GetService<SmoQuerier>(q => q.SupportedObjectTypes.Contains(smoType));
if (expectExists)
{
Assert.NotNull(querier);
Assert.Equal(querierType, querier.GetType());
Assert.NotNull(querier.ServiceProvider);
}
else
{
Assert.Null(querier);
}
}
}
}