From cd1e4f5ec571bfad9bbe4f75c7cc8f3950e58c02 Mon Sep 17 00:00:00 2001 From: Justin M <63619224+JustinMDotNet@users.noreply.github.com> Date: Tue, 8 Sep 2020 08:01:39 -0700 Subject: [PATCH] 3647 Kusto Refresh Logic (#1067) * 3647 Renamed ParentMetadata to DatabaseMetadata in FolderMetadata.cs. Removed Refresh function from IDataSource and DataSourceBase since it's not used outside of KustoDataSource. Added unit tests for ServerNode.cs. Removed unused constructor from TreeNode and unused AddChild function. * 3647 Refactored KustoDataSource. Moved Set logic for DatabaseMetadata and columns into separate functions * 3647 Reverted Rename of ParentMetadata in FolderMetadata. Reverted removal of Refresh function from DataSourceBase and IDatasource * 3647 Renamed SmoModel directories to DataSourceModel in Kusto projects. * 3647 Removed reference to Kusto.ServiceLayer in SqlTools.ServiceLayer --- .../DataSource/DataSourceBase.cs | 3 +- .../DataSource/IDataSource.cs | 3 +- .../DataSource/KustoDataSource.cs | 128 +++++++------- .../Microsoft.Kusto.ServiceLayer.csproj | 2 +- .../NodePathGenerator.cs | 0 .../ServerNode.cs | 2 +- .../SmoQueryContext.cs | 0 .../SmoTreeNode.cs | 0 .../TreeNodeDefinition.xml | 0 .../ObjectExplorer/Nodes/TreeNode.cs | 70 ++++---- .../Microsoft.SqlTools.ServiceLayer.csproj | 1 - .../DataSourceModel/ServerNodeTests.cs | 160 ++++++++++++++++++ 12 files changed, 266 insertions(+), 103 deletions(-) rename src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/{SmoModel => DataSourceModel}/NodePathGenerator.cs (100%) rename src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/{SmoModel => DataSourceModel}/ServerNode.cs (98%) rename src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/{SmoModel => DataSourceModel}/SmoQueryContext.cs (100%) rename src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/{SmoModel => DataSourceModel}/SmoTreeNode.cs (100%) rename src/Microsoft.Kusto.ServiceLayer/ObjectExplorer/{SmoModel => DataSourceModel}/TreeNodeDefinition.xml (100%) create mode 100644 test/Microsoft.Kusto.ServiceLayer.UnitTests/ObjectExplorer/DataSourceModel/ServerNodeTests.cs 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