diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs index 5f11d3a4..730f952a 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/DataSourceBase.cs @@ -77,8 +77,9 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// public abstract IEnumerable GetChildObjects(DataSourceObjectMetadata parentMetadata, bool includeSizeDetails = false); + /// /// - public abstract void Refresh(); + public abstract void Refresh(bool includeDatabase); /// public abstract void Refresh(DataSourceObjectMetadata objectMetadata); diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs index 0accf989..7f8988f4 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/IDataSource.cs @@ -73,7 +73,8 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource /// /// Refresh object list for entire cluster. /// - void Refresh(); + /// + void Refresh(bool includeDatabase); /// /// Refresh object list for given object. diff --git a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs index 7c655ac5..29ef5f13 100644 --- a/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs +++ b/src/Microsoft.Kusto.ServiceLayer/DataSource/KustoDataSource.cs @@ -347,40 +347,45 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource { if (_databaseMetadata == null) { - CancellationTokenSource source = new CancellationTokenSource(); - CancellationToken token = source.Token; - - // Getting database names when we are connected to a specific database should not happen. - ValidationUtils.IsNotNull(DatabaseName, nameof(DatabaseName)); - - var query = ".show databases" + (this.ClusterName.IndexOf(AriaProxyURL, StringComparison.CurrentCultureIgnoreCase) == -1 ? " | project DatabaseName, PrettyName" : ""); - - if (includeSizeDetails == true){ - query = ".show cluster extents | summarize sum(OriginalSize) by tostring(DatabaseName)"; - } - - using (var reader = ExecuteQuery(query, token)) - { - _databaseMetadata = reader.ToEnumerable() - .Where(row => !string.IsNullOrWhiteSpace(row["DatabaseName"].ToString())) - .Select(row => new DatabaseMetadata - { - ClusterName = this.ClusterName, - MetadataType = DataSourceMetadataType.Database, - MetadataTypeName = DataSourceMetadataType.Database.ToString(), - SizeInMB = includeSizeDetails == true ? row["sum_OriginalSize"].ToString() : null, - Name = row["DatabaseName"].ToString(), - PrettyName = includeSizeDetails == true ? row["DatabaseName"].ToString(): (String.IsNullOrEmpty(row["PrettyName"]?.ToString()) ? row["DatabaseName"].ToString() : row["PrettyName"].ToString()), - Urn = $"{this.ClusterName}.{row["DatabaseName"].ToString()}" - }) - .Materialize() - .OrderBy(row => row.Name, StringComparer.Ordinal); // case-sensitive - } + SetDatabaseMetadata(includeSizeDetails); } return _databaseMetadata; } + private void SetDatabaseMetadata(bool includeSizeDetails) + { + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken token = source.Token; + + // Getting database names when we are connected to a specific database should not happen. + ValidationUtils.IsNotNull(DatabaseName, nameof(DatabaseName)); + + var query = ".show databases" + (this.ClusterName.IndexOf(AriaProxyURL, StringComparison.CurrentCultureIgnoreCase) == -1 ? " | project DatabaseName, PrettyName" : ""); + + if (includeSizeDetails == true){ + query = ".show cluster extents | summarize sum(OriginalSize) by tostring(DatabaseName)"; + } + + using (var reader = ExecuteQuery(query, token)) + { + _databaseMetadata = reader.ToEnumerable() + .Where(row => !string.IsNullOrWhiteSpace(row["DatabaseName"].ToString())) + .Select(row => new DatabaseMetadata + { + ClusterName = this.ClusterName, + MetadataType = DataSourceMetadataType.Database, + MetadataTypeName = DataSourceMetadataType.Database.ToString(), + SizeInMB = includeSizeDetails == true ? row["sum_OriginalSize"].ToString() : null, + Name = row["DatabaseName"].ToString(), + PrettyName = includeSizeDetails == true ? row["DatabaseName"].ToString(): (String.IsNullOrEmpty(row["PrettyName"]?.ToString()) ? row["DatabaseName"].ToString() : row["PrettyName"].ToString()), + Urn = $"{this.ClusterName}.{row["DatabaseName"].ToString()}" + }) + .Materialize() + .OrderBy(row => row.Name, StringComparer.Ordinal); // case-sensitive + } + } + /// public override bool Exists(DataSourceObjectMetadata objectMetadata) { @@ -509,11 +514,17 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource return markers.ToArray(); } - /// - public override void Refresh() + /// + /// Clears everything + /// + /// + public override void Refresh(bool includeDatabase) { // This class caches objects. Throw them away so that the next call will re-query the data source for the objects. - _databaseMetadata = null; + if (includeDatabase) + { + _databaseMetadata = null; + } _tableMetadata = new ConcurrentDictionary>(); _columnMetadata = new ConcurrentDictionary>(); _folderMetadata = new ConcurrentDictionary>(); @@ -528,49 +539,45 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource switch(objectMetadata.MetadataType) { case DataSourceMetadataType.Cluster: - Refresh(); + Refresh(true); + SetDatabaseMetadata(false); break; - + case DataSourceMetadataType.Database: - _tableMetadata.TryRemove(objectMetadata.Name, out _); + Refresh(false); + LoadTableSchema(objectMetadata); + LoadFunctionSchema(objectMetadata); break; case DataSourceMetadataType.Table: - var tm = objectMetadata as TableMetadata; - _columnMetadata.TryRemove(GenerateMetadataKey(tm.DatabaseName, tm.Name), out _); + var table = objectMetadata as TableMetadata; + _columnMetadata.TryRemove(GenerateMetadataKey(table.DatabaseName, table.Name), out _); + SetTableSchema(table); break; - case DataSourceMetadataType.Column: - // Remove column metadata for the whole table - var cm = objectMetadata as ColumnMetadata; - _columnMetadata.TryRemove(GenerateMetadataKey(cm.DatabaseName, cm.TableName), out _); - break; - - case DataSourceMetadataType.Function: - var fm = objectMetadata as FunctionMetadata; - _functionMetadata.TryRemove(GenerateMetadataKey(fm.DatabaseName, fm.Name), out _); - break; - case DataSourceMetadataType.Folder: + Refresh(false); var folder = objectMetadata as FolderMetadata; - _folderMetadata.TryRemove(GenerateMetadataKey(folder.ParentMetadata.Name, folder.Name), out _); + LoadTableSchema(folder.ParentMetadata); + LoadFunctionSchema(folder.ParentMetadata); break; - + default: throw new ArgumentException($"Unexpected type {objectMetadata.MetadataType}."); } } /// - public override IEnumerable GetChildObjects(DataSourceObjectMetadata objectMetadata, bool includeSizeDetails) + public override IEnumerable GetChildObjects(DataSourceObjectMetadata objectMetadata, + bool includeSizeDetails = false) { ValidationUtils.IsNotNull(objectMetadata, nameof(objectMetadata)); - switch(objectMetadata.MetadataType) + switch (objectMetadata.MetadataType) { case DataSourceMetadataType.Cluster: // show databases return GetDatabaseMetadata(includeSizeDetails); - + case DataSourceMetadataType.Database: // show folders, tables, and functions return includeSizeDetails ? GetTablesForDashboard(objectMetadata) @@ -589,7 +596,7 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource } } - public override DiagnosticsInfo GetDiagnostics(DataSourceObjectMetadata objectMetadata) + public override DiagnosticsInfo GetDiagnostics(DataSourceObjectMetadata objectMetadata) { ValidationUtils.IsNotNull(objectMetadata, nameof(objectMetadata)); @@ -765,16 +772,23 @@ namespace Microsoft.Kusto.ServiceLayer.DataSource return _columnMetadata[key]; } + SetTableSchema(tableMetadata); + + return _columnMetadata.ContainsKey(key) + ? _columnMetadata[key] + : Enumerable.Empty(); + } + + private void SetTableSchema(TableMetadata tableMetadata) + { IEnumerable columnInfos = GetColumnInfos(tableMetadata.DatabaseName, tableMetadata.Name); if (!columnInfos.Any()) { - return Enumerable.Empty(); + return; } SetColumnMetadata(tableMetadata.DatabaseName, tableMetadata.Name, columnInfos); - - return _columnMetadata[key]; } private void SetFolderMetadataForTables(DataSourceObjectMetadata objectMetadata, IEnumerable tableInfos, string rootTableFolderKey) diff --git a/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj b/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj index 2d5d038b..fcff7eec 100644 --- a/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj +++ b/src/Microsoft.Kusto.ServiceLayer/Microsoft.Kusto.ServiceLayer.csproj @@ -36,7 +36,7 @@ - + diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/NodePathGenerator.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/NodePathGenerator.cs similarity index 100% rename from src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/NodePathGenerator.cs rename to src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/NodePathGenerator.cs diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/ServerNode.cs similarity index 98% rename from src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs rename to src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/ServerNode.cs index 3b8562fb..afad6d11 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/ServerNode.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/ServerNode.cs @@ -47,7 +47,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel /// /// Returns the label to display to the user. /// - internal string GetConnectionLabel() + private string GetConnectionLabel() { string userName = connectionSummary.UserName; diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/SmoQueryContext.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/SmoQueryContext.cs similarity index 100% rename from src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/SmoQueryContext.cs rename to src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/SmoQueryContext.cs diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNode.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/SmoTreeNode.cs similarity index 100% rename from src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/SmoTreeNode.cs rename to src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/SmoTreeNode.cs diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/TreeNodeDefinition.xml b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/TreeNodeDefinition.xml similarity index 100% rename from src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/SmoModel/TreeNodeDefinition.xml rename to src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/DataSourceModel/TreeNodeDefinition.xml diff --git a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs index b3be72d5..86d2a09f 100644 --- a/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs +++ b/src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/Nodes/TreeNode.cs @@ -30,7 +30,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes private string nodePath; private string label; private string nodePathName; - public const char PathPartSeperator = '/'; + private const char PathPartSeperator = '/'; /// /// Object metadata @@ -43,27 +43,17 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes public IDataSource DataSource { get; set; } /// - /// Constructor with no required inputs + /// Constructor with DataSource and DataSourceObjectMetadata /// - public TreeNode(IDataSource dataSource, DataSourceObjectMetadata objectMetadata) + /// + /// + protected TreeNode(IDataSource dataSource, DataSourceObjectMetadata objectMetadata) { DataSource = dataSource; ObjectMetadata = objectMetadata; NodeValue = objectMetadata.Name; } - /// - /// Constructor that accepts a label to identify the node - /// - /// Label identifying the node - public TreeNode(string value, IDataSource dataSource, DataSourceObjectMetadata objectMetadata) - : this(dataSource, objectMetadata) - { - // We intentionally do not valid this being null or empty since - // some nodes may need to set it - NodeValue = value; - } - private object buildingMetadataLock = new object(); /// @@ -133,7 +123,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes /// for many nodes such as the server, the display label will be different /// to the value. /// - public string Label { + protected string Label { get { if(label == null) @@ -290,17 +280,6 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes return new ReadOnlyCollection(children); } - /// - /// Adds a child to the list of children under this node - /// - /// - public void AddChild(TreeNode newChild) - { - Validate.IsNotNull(nameof(newChild), newChild); - children.Add(newChild); - newChild.Parent = this; - } - /// /// Optional context to help with lookup of children /// @@ -326,7 +305,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes return Parent as T; } - protected virtual void PopulateChildren(bool refresh, string name, CancellationToken cancellationToken) + protected void PopulateChildren(bool refresh, string name, CancellationToken cancellationToken) { Logger.Write(TraceEventType.Verbose, string.Format(CultureInfo.InvariantCulture, "Populating oe node :{0}", this.GetNodePath())); Debug.Assert(IsAlwaysLeaf == false); @@ -368,25 +347,22 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes } } - protected IEnumerable ExpandChildren(TreeNode parent, bool refresh, string name, bool includeSystemObjects, CancellationToken cancellationToken) + protected IEnumerable ExpandChildren(TreeNode parent, bool refresh, string name, + bool includeSystemObjects, CancellationToken cancellationToken) { - List allChildren = new List(); - try { - OnExpandPopulateNonFolders(allChildren, parent, refresh, name, cancellationToken); + return OnExpandPopulateNonFolders(parent, refresh, name, cancellationToken); } - catch(Exception ex) + catch (Exception ex) { - string error = string.Format(CultureInfo.InvariantCulture, "Failed expanding oe children. parent:{0} error:{1} inner:{2} stacktrace:{3}", - parent != null ? parent.GetNodePath() : "", ex.Message, ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); + string error = string.Format(CultureInfo.InvariantCulture, + "Failed expanding oe children. parent:{0} error:{1} inner:{2} stacktrace:{3}", + parent != null ? parent.GetNodePath() : "", ex.Message, + ex.InnerException != null ? ex.InnerException.Message : "", ex.StackTrace); Logger.Write(TraceEventType.Error, error); throw ex; } - finally - { - } - return allChildren; } /// @@ -394,7 +370,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes /// /// List to which nodes should be added /// Parent the nodes are being added to - private void OnExpandPopulateNonFolders(IList allChildren, TreeNode parent, bool refresh, string name, CancellationToken cancellationToken) + private List OnExpandPopulateNonFolders(TreeNode parent, bool refresh, string name, CancellationToken cancellationToken) { Logger.Write(TraceEventType.Verbose, string.Format(CultureInfo.InvariantCulture, "child factory parent :{0}", parent.GetNodePath())); @@ -406,9 +382,15 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes if (parent.DataSource != null) { + if (refresh) + { + parent.DataSource.Refresh(parent.ObjectMetadata); + } + objectMetadataList = parent.DataSource.GetChildObjects(parent.ObjectMetadata); } + List allChildren = new List(); foreach (var objectMetadata in objectMetadataList) { cancellationToken.ThrowIfCancellationRequested(); @@ -423,6 +405,7 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes } } + return allChildren; } catch (Exception ex) { @@ -436,7 +419,12 @@ namespace Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes /// /// The glue between the DataSource and the Object Explorer models. Creates the right tree node for each data source type /// - protected TreeNode CreateChild(TreeNode parent, DataSourceObjectMetadata childMetadata) + /// + /// + /// + /// + + private TreeNode CreateChild(TreeNode parent, DataSourceObjectMetadata childMetadata) { ValidationUtils.IsNotNull(parent, nameof(parent)); ValidationUtils.IsNotNull(childMetadata, nameof(childMetadata)); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj index 203eeaea..e878c991 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj +++ b/src/Microsoft.SqlTools.ServiceLayer/Microsoft.SqlTools.ServiceLayer.csproj @@ -33,7 +33,6 @@ - diff --git a/test/Microsoft.Kusto.ServiceLayer.UnitTests/ObjectExplorer/DataSourceModel/ServerNodeTests.cs b/test/Microsoft.Kusto.ServiceLayer.UnitTests/ObjectExplorer/DataSourceModel/ServerNodeTests.cs new file mode 100644 index 00000000..4407226f --- /dev/null +++ b/test/Microsoft.Kusto.ServiceLayer.UnitTests/ObjectExplorer/DataSourceModel/ServerNodeTests.cs @@ -0,0 +1,160 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.Kusto.ServiceLayer.Connection.Contracts; +using Microsoft.Kusto.ServiceLayer.DataSource; +using Microsoft.Kusto.ServiceLayer.DataSource.Metadata; +using Microsoft.Kusto.ServiceLayer.ObjectExplorer.DataSourceModel; +using Microsoft.Kusto.ServiceLayer.ObjectExplorer.Nodes; +using Microsoft.Kusto.ServiceLayer.Utility; +using Microsoft.SqlTools.Extensibility; +using Moq; +using NUnit.Framework; + +namespace Microsoft.Kusto.ServiceLayer.UnitTests.ObjectExplorer.DataSourceModel +{ + public class ServerNodeTests + { + [Test] + public void ServerNode_ThrowsException_NullConnectionParam() + { + ConnectionCompleteParams param = null; + var serviceProviderMock = new Mock(); + + Assert.Throws(() => new ServerNode(param, serviceProviderMock.Object, null, new DatabaseMetadata())); + } + + [Test] + public void ServerNode_ThrowsException_NullConnectionSummary() + { + var param = new ConnectionCompleteParams + { + ConnectionSummary = null + }; + + var serviceProviderMock = new Mock(); + + Assert.Throws(() => new ServerNode(param, serviceProviderMock.Object, null, new DatabaseMetadata())); + } + + [Test] + public void ServerNode_ThrowsException_NullServiceProvider() + { + var param = new ConnectionCompleteParams + { + ConnectionSummary = new ConnectionSummary() + }; + + Assert.Throws(() => new ServerNode(param, null, null, new DatabaseMetadata())); + } + + [TestCase(CommonConstants.MasterDatabaseName, "User1", "Server Name (SQL Server 2020 - User1)")] + [TestCase(CommonConstants.MasterDatabaseName, "", "Server Name (SQL Server 2020)")] + [TestCase(CommonConstants.MsdbDatabaseName, "User2", "Server Name (SQL Server 2020 - User2)")] + [TestCase(CommonConstants.MsdbDatabaseName, "", "Server Name (SQL Server 2020)")] + [TestCase(CommonConstants.ModelDatabaseName, "User3", "Server Name (SQL Server 2020 - User3)")] + [TestCase(CommonConstants.ModelDatabaseName, "", "Server Name (SQL Server 2020)")] + [TestCase(CommonConstants.TempDbDatabaseName, "User4", "Server Name (SQL Server 2020 - User4)")] + [TestCase(CommonConstants.TempDbDatabaseName, "", "Server Name (SQL Server 2020)")] + [TestCase("Database1", "User5", "Server Name (SQL Server 2020 - User5, Database1)")] + [TestCase("Database1", "", "Server Name (SQL Server 2020 - Database1)")] + public void GetConnectionLabel_Sets_Label_For_Params(string databaseName, string userName, string expected) + { + var connectionParams = new ConnectionCompleteParams + { + ConnectionSummary = new ConnectionSummary + { + DatabaseName = databaseName, + UserName = userName, + ServerName = "Server Name" + }, + ServerInfo = new ServerInfo + { + ServerVersion = "2020" + } + }; + + var serviceProviderMock = new Mock(); + var dataSourceMock = new Mock(); + var metadata = new DataSourceObjectMetadata(); + + var serverNode = new ServerNode(connectionParams, serviceProviderMock.Object, dataSourceMock.Object, + metadata); + + var nodeInfo = serverNode.ToNodeInfo(); + Assert.AreEqual(expected, nodeInfo.Label); + } + + [Test] + public void Refresh_Calls_DataSourceRefresh() + { + var connectionParams = new ConnectionCompleteParams + { + ConnectionSummary = new ConnectionSummary + { + UserName = "UserName", + DatabaseName = "DatabaseName", + ServerName = "ServerName" + }, + ServerInfo = new ServerInfo + { + ServerVersion = "Version" + } + }; + + var serviceProviderMock = new Mock(); + var dataSourceMock = new Mock(); + var metadata = new DataSourceObjectMetadata(); + + var serverNode = new ServerNode(connectionParams, serviceProviderMock.Object, dataSourceMock.Object, + metadata); + + serverNode.Refresh(CancellationToken.None); + + dataSourceMock.Verify(x => x.Refresh(It.IsAny()), Times.Once()); + } + + [Test] + public void Refresh_Returns_Children() + { + var childMetadata = new DataSourceObjectMetadata + { + MetadataTypeName = DataSourceMetadataType.Database.ToString(), + MetadataType = DataSourceMetadataType.Database, + Name = "Database1" + }; + + var connectionParams = new ConnectionCompleteParams + { + ConnectionSummary = new ConnectionSummary + { + UserName = "UserName", + DatabaseName = "DatabaseName", + ServerName = "ServerName" + }, + ServerInfo = new ServerInfo + { + ServerVersion = "Version" + } + }; + + var serviceProviderMock = new Mock(); + var dataSourceMock = new Mock(); + dataSourceMock.Setup(x => x.GetChildObjects(It.IsAny(), It.IsAny())) + .Returns(new List {childMetadata}); + + var parentMetadata = new DataSourceObjectMetadata(); + var serverNode = new ServerNode(connectionParams, serviceProviderMock.Object, dataSourceMock.Object, + parentMetadata); + + var children = serverNode.Refresh(CancellationToken.None); + Assert.AreEqual(1, children.Count); + var child = children.First(); + + Assert.AreEqual(childMetadata.MetadataTypeName, child.NodeType); + Assert.AreEqual(NodeTypes.Database, child.NodeTypeId); + Assert.AreEqual(childMetadata.Name, child.NodeValue); + } + } +} \ No newline at end of file