From acbc4638dd1f2cb0e025956886f6121a62fbaff3 Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Fri, 30 Aug 2024 11:19:48 +0200 Subject: [PATCH] SLVS-1376 Binding Dialog: list connections (#5649) [SLVS-1376](https://sonarsource.atlassian.net/browse/SLVS-1376) [SLVS-1376]: https://sonarsource.atlassian.net/browse/SLVS-1376?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ --- ...ServerConnectionsRepositoryAdapterTests.cs | 125 ++++++++++ .../UI/ConnectedModeServicesTests.cs | 1 + .../ManageBindingViewModelTests.cs | 219 ++++++++++++++---- .../ManageConnectionsViewModelTest.cs | 101 ++++++-- .../UI/ProgressReporterViewModelTests.cs | 2 + .../ServerConnectionsRepositoryAdapter.cs | 53 +++++ src/ConnectedMode/UI/ConnectedModeServices.cs | 5 +- .../UI/ManageBinding/ManageBindingDialog.xaml | 2 +- .../ManageBinding/ManageBindingDialog.xaml.cs | 7 +- .../ManageBinding/ManageBindingViewModel.cs | 51 +++- .../ManageConnectionsDialog.xaml | 27 ++- .../ManageConnectionsDialog.xaml.cs | 7 +- .../ManageConnectionsViewModel.cs | 28 ++- .../UI/ProgressReporterViewModel.cs | 1 + .../UI/Resources/UiResources.Designer.cs | 18 ++ .../UI/Resources/UiResources.resx | 6 + .../Binding/IServerConnectionsRepository.cs | 2 +- 17 files changed, 564 insertions(+), 91 deletions(-) create mode 100644 src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs create mode 100644 src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs diff --git a/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs new file mode 100644 index 0000000000..195877ca47 --- /dev/null +++ b/src/ConnectedMode.UnitTests/ServerConnectionsRepositoryAdapterTests.cs @@ -0,0 +1,125 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.TestInfrastructure; +using static SonarLint.VisualStudio.Core.Binding.ServerConnection; + +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests; + +[TestClass] +public class ServerConnectionsRepositoryAdapterTests +{ + private IServerConnectionsRepository serverConnectionsRepository; + private ServerConnectionsRepositoryAdapter testSubject; + + [TestInitialize] + public void TestInitialize() + { + serverConnectionsRepository = Substitute.For(); + testSubject = new ServerConnectionsRepositoryAdapter(serverConnectionsRepository); + } + + [TestMethod] + public void MefCtor_CheckIsExported() + => MefTestHelpers.CheckTypeCanBeImported(MefTestHelpers.CreateExport()); + + [TestMethod] + public void GetAllConnections_CallServerConnectionsRepository() + { + serverConnectionsRepository.GetAll().Returns([]); + + var connections = testSubject.GetAllConnections(); + + serverConnectionsRepository.Received(1).GetAll(); + connections.Should().BeEmpty(); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void GetAllConnections_HasOneSonarCloudConnection_ReturnsOneMappedConnection(bool isSmartNotificationsEnabled) + { + var sonarCloud = CreateSonarCloudServerConnection(isSmartNotificationsEnabled); + serverConnectionsRepository.GetAll().Returns([sonarCloud]); + + var connections = testSubject.GetAllConnections(); + + connections.Should().BeEquivalentTo([new Connection(new ConnectionInfo(sonarCloud.Id, ConnectionServerType.SonarCloud), isSmartNotificationsEnabled)]); + } + + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void GetAllConnections_HasOneSonarQubeConnection_ReturnsOneMappedConnection(bool isSmartNotificationsEnabled) + { + var sonarQube = CreateSonarQubeServerConnection(isSmartNotificationsEnabled); + serverConnectionsRepository.GetAll().Returns([sonarQube]); + + var connections = testSubject.GetAllConnections(); + + connections.Should().BeEquivalentTo([new Connection(new ConnectionInfo(sonarQube.Id, ConnectionServerType.SonarQube), isSmartNotificationsEnabled)]); + } + + [TestMethod] + public void GetAllConnectionsInfo_CallServerConnectionsRepository() + { + serverConnectionsRepository.GetAll().Returns([]); + + var connections = testSubject.GetAllConnectionsInfo(); + + serverConnectionsRepository.Received(1).GetAll(); + connections.Should().BeEmpty(); + } + + [TestMethod] + public void GetAllConnectionsInfo_HasOneSonarCloudConnection_ReturnsOneMappedConnection() + { + var sonarCloud = CreateSonarCloudServerConnection(); + serverConnectionsRepository.GetAll().Returns([sonarCloud]); + + var connections = testSubject.GetAllConnectionsInfo(); + + connections.Should().BeEquivalentTo([new ConnectionInfo(sonarCloud.Id, ConnectionServerType.SonarCloud)]); + } + + [TestMethod] + public void GetAllConnectionsInfo_HasOneSonarQubeConnection_ReturnsOneMappedConnection() + { + var sonarQube = CreateSonarQubeServerConnection(); + serverConnectionsRepository.GetAll().Returns([sonarQube]); + + var connections = testSubject.GetAllConnectionsInfo(); + + connections.Should().BeEquivalentTo([new ConnectionInfo(sonarQube.Id, ConnectionServerType.SonarQube)]); + } + + private static SonarCloud CreateSonarCloudServerConnection(bool isSmartNotificationsEnabled = true) + { + return new SonarCloud("myOrg", new ServerConnectionSettings(isSmartNotificationsEnabled), Substitute.For()); + } + + private static ServerConnection.SonarQube CreateSonarQubeServerConnection(bool isSmartNotificationsEnabled = true) + { + var sonarQube = new ServerConnection.SonarQube(new Uri("http://localhost"), new ServerConnectionSettings(isSmartNotificationsEnabled), Substitute.For()); + return sonarQube; + } +} diff --git a/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs b/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs index 0df65af841..9dd7fab8bd 100644 --- a/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs +++ b/src/ConnectedMode.UnitTests/UI/ConnectedModeServicesTests.cs @@ -38,6 +38,7 @@ public void MefCtor_CheckIsExported() MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), MefTestHelpers.CreateExport()); } } diff --git a/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs b/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs index bea1f9c25d..01957d2cc7 100644 --- a/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs +++ b/src/ConnectedMode.UnitTests/UI/ManageBinding/ManageBindingViewModelTests.cs @@ -19,8 +19,11 @@ */ using System.ComponentModel; +using SonarLint.VisualStudio.ConnectedMode.UI; using SonarLint.VisualStudio.ConnectedMode.UI.ManageBinding; using SonarLint.VisualStudio.ConnectedMode.UI.ProjectSelection; +using SonarLint.VisualStudio.ConnectedMode.UI.Resources; +using SonarLint.VisualStudio.Core; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI.ManageBinding; @@ -32,11 +35,20 @@ public class ManageBindingViewModelTests private readonly ServerProject serverProject = new ("a-project", "A Project"); private readonly ConnectionInfo sonarQubeConnectionInfo = new ("http://localhost:9000", ConnectionServerType.SonarQube); private readonly ConnectionInfo sonarCloudConnectionInfo = new ("http://sonarcloud.io", ConnectionServerType.SonarCloud); + private IServerConnectionsRepositoryAdapter serverConnectionsRepositoryAdapter; + private IConnectedModeServices connectedModeServices; + private IProgressReporterViewModel progressReporterViewModel; + private IThreadHandling threadHandling; + private ILogger logger; [TestInitialize] public void TestInitialize() { - testSubject = new ManageBindingViewModel(solutionInfoModel); + connectedModeServices = Substitute.For(); + progressReporterViewModel = Substitute.For(); + testSubject = new ManageBindingViewModel(connectedModeServices, solutionInfoModel, progressReporterViewModel); + + MockServices(); } [TestMethod] @@ -201,7 +213,7 @@ public void Unbind_SetsConnectionInfoToNull() public void IsBindButtonEnabled_ProjectIsSelectedAndBindingIsNotInProgress_ReturnsTrue() { testSubject.SelectedProject = serverProject; - testSubject.ProgressReporter.ProgressStatus = null; + progressReporterViewModel.IsOperationInProgress.Returns(false); testSubject.IsBindButtonEnabled.Should().BeTrue(); } @@ -210,50 +222,50 @@ public void IsBindButtonEnabled_ProjectIsSelectedAndBindingIsNotInProgress_Retur public void IsBindButtonEnabled_ProjectIsSelectedAndBindingIsInProgress_ReturnsFalse() { testSubject.SelectedProject = serverProject; - testSubject.ProgressReporter.ProgressStatus = "in progress"; + progressReporterViewModel.IsOperationInProgress.Returns(true); testSubject.IsBindButtonEnabled.Should().BeFalse(); } [TestMethod] - [DataRow("binding...")] - [DataRow(null)] - public void IsBindButtonEnabled_ProjectIsNotSelected_ReturnsFalse(string bindingStatus) + [DataRow(true)] + [DataRow(false)] + public void IsBindButtonEnabled_ProjectIsNotSelected_ReturnsFalse(bool isBindingInProgress) { testSubject.SelectedProject = null; - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.IsBindButtonEnabled.Should().BeFalse(); } [TestMethod] - [DataRow("binding...", false)] - [DataRow(null, true)] - public void IsManageConnectionsButtonEnabled_ReturnsTrueOnlyWhenNoBindingIsInProgress(string bindingStatus, bool expectedResult) + [DataRow(true, false)] + [DataRow(false, true)] + public void IsManageConnectionsButtonEnabled_ReturnsTrueOnlyWhenNoBindingIsInProgress(bool isBindingInProgress, bool expectedResult) { - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.IsManageConnectionsButtonEnabled.Should().Be(expectedResult); } [TestMethod] - [DataRow("binding...", false)] - [DataRow(null, true)] - public void IsUseSharedBindingButtonEnabled_SharedBindingConfigurationIsDetected_ReturnsTrueOnlyWhenNoBindingIsInProgress(string bindingStatus, bool expectedResult) + [DataRow(true, false)] + [DataRow(false, true)] + public void IsUseSharedBindingButtonEnabled_SharedBindingConfigurationIsDetected_ReturnsTrueOnlyWhenNoBindingIsInProgress(bool isBindingInProgress, bool expectedResult) { testSubject.IsSharedBindingConfigurationDetected = true; - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.IsUseSharedBindingButtonEnabled.Should().Be(expectedResult); } [TestMethod] - [DataRow("binding...")] + [DataRow(true)] [DataRow(null)] - public void IsUseSharedBindingButtonEnabled_SharedBindingConfigurationIsNotDetected_ReturnsFalse(string bindingStatus) + public void IsUseSharedBindingButtonEnabled_SharedBindingConfigurationIsNotDetected_ReturnsFalse(bool isBindingInProgress) { testSubject.IsSharedBindingConfigurationDetected = false; - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.IsUseSharedBindingButtonEnabled.Should().BeFalse(); } @@ -274,11 +286,11 @@ public void IsSharedBindingConfigurationDetected_Set_RaisesEvents() } [TestMethod] - [DataRow("binding...", false)] - [DataRow(null, true)] - public void IsUnbindButtonEnabled_ReturnsTrueOnlyWhenNoBindingIsInProgress(string bindingStatus, bool expectedResult) + [DataRow(true, false)] + [DataRow(false, true)] + public void IsUnbindButtonEnabled_ReturnsTrueOnlyWhenNoBindingIsInProgress(bool isBindingInProgress, bool expectedResult) { - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.IsUnbindButtonEnabled.Should().Be(expectedResult); } @@ -287,7 +299,7 @@ public void IsUnbindButtonEnabled_ReturnsTrueOnlyWhenNoBindingIsInProgress(strin public void IsSelectProjectButtonEnabled_ConnectionIsSelectedAndNoBindingIsInProgressAndProjectIsNotBound_ReturnsTrue() { testSubject.BoundProject = null; - testSubject.ProgressReporter.ProgressStatus = null; + progressReporterViewModel.IsOperationInProgress.Returns(false); testSubject.SelectedConnectionInfo = sonarQubeConnectionInfo; testSubject.IsSelectProjectButtonEnabled.Should().BeTrue(); @@ -297,18 +309,18 @@ public void IsSelectProjectButtonEnabled_ConnectionIsSelectedAndNoBindingIsInPro public void IsSelectProjectButtonEnabled_BindingIsInProgress_ReturnsFalse() { testSubject.BoundProject = null; - testSubject.ProgressReporter.ProgressStatus = "in progress"; + progressReporterViewModel.IsOperationInProgress.Returns(true); testSubject.SelectedConnectionInfo = sonarQubeConnectionInfo; testSubject.IsSelectProjectButtonEnabled.Should().BeFalse(); } [TestMethod] - [DataRow("binding...")] - [DataRow(null)] - public void IsSelectProjectButtonEnabled_ConnectionIsNotSelected_ReturnsFalse(string bindingStatus) + [DataRow(true)] + [DataRow(false)] + public void IsSelectProjectButtonEnabled_ConnectionIsNotSelected_ReturnsFalse(bool isBindingInProgress) { - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.SelectedConnectionInfo = null; testSubject.IsSelectProjectButtonEnabled.Should().BeFalse(); @@ -318,7 +330,7 @@ public void IsSelectProjectButtonEnabled_ConnectionIsNotSelected_ReturnsFalse(st public void IsSelectProjectButtonEnabled_ProjectIsAlreadyBound_ReturnsFalse() { testSubject.BoundProject = serverProject; - testSubject.ProgressReporter.ProgressStatus = null; + progressReporterViewModel.IsOperationInProgress.Returns(false); testSubject.SelectedConnectionInfo = sonarQubeConnectionInfo; testSubject.IsSelectProjectButtonEnabled.Should().BeFalse(); @@ -328,33 +340,44 @@ public void IsSelectProjectButtonEnabled_ProjectIsAlreadyBound_ReturnsFalse() public void IsConnectionSelectionEnabled_BindingIsInProgress_ReturnsFalse() { testSubject.BoundProject = null; - testSubject.ProgressReporter.ProgressStatus = "in progress"; + progressReporterViewModel.IsOperationInProgress.Returns(true); testSubject.IsConnectionSelectionEnabled.Should().BeFalse(); } [TestMethod] - [DataRow(null)] - [DataRow("binding...")] - public void IsConnectionSelectionEnabled_ProjectIsBound_ReturnsFalse(string bindingStatus) + [DataRow(false)] + [DataRow(true)] + public void IsConnectionSelectionEnabled_ProjectIsBound_ReturnsFalse(bool isBindingInProgress) { testSubject.BoundProject = serverProject; - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.IsConnectionSelectionEnabled.Should().BeFalse(); } [TestMethod] - public void IsConnectionSelectionEnabled_ProjectIsNotBoundAndBindingIsNotInProgress_ReturnsTrue() + public void IsConnectionSelectionEnabled_NoConnectionsExist_ReturnsFalse() { testSubject.BoundProject = null; - testSubject.ProgressReporter.ProgressStatus = null; + progressReporterViewModel.IsOperationInProgress.Returns(false); + + testSubject.IsConnectionSelectionEnabled.Should().BeFalse(); + } + + [TestMethod] + public void IsConnectionSelectionEnabled_ProjectIsNotBoundAndBindingIsNotInProgressAndConnectionsExist_ReturnsTrue() + { + testSubject.BoundProject = null; + progressReporterViewModel.IsOperationInProgress.Returns(false); + connectedModeServices.ServerConnectionsRepositoryAdapter.GetAllConnectionsInfo().Returns([sonarCloudConnectionInfo]); + testSubject.LoadConnections(); testSubject.IsConnectionSelectionEnabled.Should().BeTrue(); } [TestMethod] - public void ProgressStatus_Set_RaisesEvents() + public void UpdateProgress_RaisesEvents() { var eventHandler = Substitute.For(); testSubject.PropertyChanged += eventHandler; @@ -382,18 +405,18 @@ public void ProgressStatus_Set_RaisesEvents() public void IsExportButtonEnabled_BindingIsInProgress_ReturnsFalse() { testSubject.BoundProject = serverProject; - testSubject.ProgressReporter.ProgressStatus = "in progress"; + progressReporterViewModel.IsOperationInProgress.Returns(true); testSubject.IsExportButtonEnabled.Should().BeFalse(); } [TestMethod] - [DataRow(null)] - [DataRow("binding...")] - public void IsExportButtonEnabled_ProjectIsNotBound_ReturnsFalse(string bindingStatus) + [DataRow(false)] + [DataRow(true)] + public void IsExportButtonEnabled_ProjectIsNotBound_ReturnsFalse(bool isBindingInProgress) { testSubject.BoundProject = null; - testSubject.ProgressReporter.ProgressStatus = bindingStatus; + progressReporterViewModel.IsOperationInProgress.Returns(isBindingInProgress); testSubject.IsExportButtonEnabled.Should().BeFalse(); } @@ -402,8 +425,118 @@ public void IsExportButtonEnabled_ProjectIsNotBound_ReturnsFalse(string bindingS public void IsExportButtonEnabled_ProjectIsBoundAndBindingIsNotInProgress_ReturnsTrue() { testSubject.BoundProject = serverProject; - testSubject.ProgressReporter.ProgressStatus = null; + progressReporterViewModel.IsOperationInProgress.Returns(false); testSubject.IsExportButtonEnabled.Should().BeTrue(); } + + [TestMethod] + public void LoadConnections_FillsConnections() + { + List existingConnections = [sonarQubeConnectionInfo, sonarCloudConnectionInfo]; + serverConnectionsRepositoryAdapter.GetAllConnectionsInfo().Returns(existingConnections); + + testSubject.LoadConnections(); + + testSubject.Connections.Should().BeEquivalentTo(existingConnections); + } + + [TestMethod] + public void LoadConnections_ClearsPreviousConnections() + { + serverConnectionsRepositoryAdapter.GetAllConnectionsInfo().Returns([sonarQubeConnectionInfo]); + testSubject.Connections.Add(sonarCloudConnectionInfo); + + testSubject.LoadConnections(); + + testSubject.Connections.Should().BeEquivalentTo([sonarQubeConnectionInfo]); + } + + [TestMethod] + public void LoadConnections_RaisesEvents() + { + var eventHandler = Substitute.For(); + testSubject.PropertyChanged += eventHandler; + eventHandler.ReceivedCalls().Should().BeEmpty(); + + testSubject.LoadConnections(); + + eventHandler.Received().Invoke(testSubject, + Arg.Is(x => x.PropertyName == nameof(testSubject.IsConnectionSelectionEnabled))); + eventHandler.Received().Invoke(testSubject, + Arg.Is(x => x.PropertyName == nameof(testSubject.ConnectionSelectionCaptionText))); + } + + [TestMethod] + public async Task InitializeDataAsync_InitializesDataAndReportsProgress() + { + await testSubject.InitializeDataAsync(); + + await progressReporterViewModel.Received(1) + .ExecuteTaskWithProgressAsync( + Arg.Is>(x => + x.TaskToPerform == testSubject.LoadDataAsync && + x.ProgressStatus == UiResources.LoadingConnectionsText && + x.WarningText == UiResources.LoadingConnectionsFailedText && + x.AfterProgressUpdated == testSubject.OnProgressUpdated)); + } + + [TestMethod] + public async Task LoadDataAsync_LoadsConnections() + { + await testSubject.LoadDataAsync(); + + await threadHandling.Received(1).RunOnUIThreadAsync(Arg.Is(op => op == testSubject.LoadConnections)); + } + + [TestMethod] + public async Task LoadDataAsync_ConnectionsLoadedSuccessfully_ReturnsTrue() + { + var adapterResponse = await testSubject.LoadDataAsync(); + + adapterResponse.Success.Should().BeTrue(); + } + + [TestMethod] + public async Task LoadDataAsync_LoadingConnectionsThrows_ReturnsFalse() + { + var exceptionMsg = "Failed to load connections"; + var mockedThreadHandling = Substitute.For(); + connectedModeServices.ThreadHandling.Returns(mockedThreadHandling); + mockedThreadHandling.When(x => x.RunOnUIThreadAsync(Arg.Any())).Do(callInfo=> throw new Exception(exceptionMsg)); + + var adapterResponse = await testSubject.LoadDataAsync(); + + adapterResponse.Success.Should().BeFalse(); + logger.Received(1).WriteLine(exceptionMsg); + } + + [TestMethod] + public void ConnectionSelectionCaptionText_ConnectionsExists_ReturnsSelectConnectionToBindDescription() + { + testSubject.Connections.Add(sonarCloudConnectionInfo); + + testSubject.ConnectionSelectionCaptionText.Should().Be(UiResources.SelectConnectionToBindDescription); + } + + [TestMethod] + public void ConnectionSelectionCaptionText_NoConnectionExists_ReturnsNoConnectionExistsLabel() + { + testSubject.SelectedConnectionInfo = null; + + testSubject.ConnectionSelectionCaptionText.Should().Be(UiResources.NoConnectionExistsLabel); + } + + private void MockServices() + { + serverConnectionsRepositoryAdapter = Substitute.For(); + threadHandling = Substitute.For(); + logger = Substitute.For(); + + connectedModeServices.ServerConnectionsRepositoryAdapter.Returns(serverConnectionsRepositoryAdapter); + connectedModeServices.ThreadHandling.Returns(threadHandling); + connectedModeServices.Logger.Returns(logger); + + serverConnectionsRepositoryAdapter.GetAllConnectionsInfo().Returns([]); + } } diff --git a/src/ConnectedMode.UnitTests/UI/ManageConnections/ManageConnectionsViewModelTest.cs b/src/ConnectedMode.UnitTests/UI/ManageConnections/ManageConnectionsViewModelTest.cs index 25a3f0ccdc..f30f83b0dd 100644 --- a/src/ConnectedMode.UnitTests/UI/ManageConnections/ManageConnectionsViewModelTest.cs +++ b/src/ConnectedMode.UnitTests/UI/ManageConnections/ManageConnectionsViewModelTest.cs @@ -19,7 +19,10 @@ */ using System.ComponentModel; +using SonarLint.VisualStudio.ConnectedMode.UI; using SonarLint.VisualStudio.ConnectedMode.UI.ManageConnections; +using SonarLint.VisualStudio.ConnectedMode.UI.Resources; +using SonarLint.VisualStudio.Core; namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI.ManageConnections; @@ -27,7 +30,12 @@ namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.UI.ManageConnections; public class ManageConnectionsViewModelTest { private ManageConnectionsViewModel testSubject; - private IEnumerable connections; + private List connections; + private IProgressReporterViewModel progressReporterViewModel; + private IConnectedModeServices connectedModeServices; + private IServerConnectionsRepositoryAdapter serverConnectionsRepositoryAdapter; + private IThreadHandling threadHandling; + private ILogger logger; [TestInitialize] public void TestInitialize() @@ -37,7 +45,12 @@ public void TestInitialize() new Connection(new ConnectionInfo("http://localhost:9000", ConnectionServerType.SonarQube), true), new Connection(new ConnectionInfo("https://sonarcloud.io/myOrg", ConnectionServerType.SonarCloud), false) ]; - testSubject = new ManageConnectionsViewModel(); + progressReporterViewModel = Substitute.For(); + connectedModeServices = Substitute.For(); + + testSubject = new ManageConnectionsViewModel(connectedModeServices, progressReporterViewModel); + + MockServices(); } [TestMethod] @@ -49,8 +62,10 @@ public void ConnectionViewModels_NoInitialization_HasEmptyList() [TestMethod] public void InitializeConnections_InitializesConnectionsCorrectly() - { - testSubject.InitializeConnections(connections); + { + serverConnectionsRepositoryAdapter.GetAllConnections().Returns(connections); + + testSubject.InitializeConnections(); HasExpectedConnections(connections); } @@ -58,8 +73,8 @@ public void InitializeConnections_InitializesConnectionsCorrectly() [TestMethod] public void RemoveConnection_RemovesProvidedConnection() { - testSubject.InitializeConnections(connections); - var connectionToRemove = testSubject.ConnectionViewModels.First(); + InitializeTwoConnections(); + var connectionToRemove = testSubject.ConnectionViewModels[0]; testSubject.RemoveConnection(connectionToRemove); @@ -70,9 +85,9 @@ public void RemoveConnection_RemovesProvidedConnection() [TestMethod] public void RemoveConnection_RaisesEvents() { + InitializeTwoConnections(); var eventHandler = Substitute.For(); testSubject.PropertyChanged += eventHandler; - testSubject.InitializeConnections(connections); testSubject.RemoveConnection(testSubject.ConnectionViewModels[0]); @@ -82,14 +97,12 @@ public void RemoveConnection_RaisesEvents() [TestMethod] public void AddConnection_AddsProvidedConnection() { - testSubject.InitializeConnections(connections); var connectionToAdd = new Connection(new ConnectionInfo("https://sonarcloud.io/mySecondOrg", ConnectionServerType.SonarCloud), false); testSubject.AddConnection(connectionToAdd); - var viewModelsCount = testSubject.ConnectionViewModels.Count; - viewModelsCount.Should().Be(connections.Count() + 1); - testSubject.ConnectionViewModels[viewModelsCount - 1].Connection.Should().Be(connectionToAdd); + testSubject.ConnectionViewModels.Count.Should().Be( 1); + testSubject.ConnectionViewModels[0].Connection.Should().Be(connectionToAdd); } [TestMethod] @@ -97,7 +110,6 @@ public void AddConnection_RaisesEvents() { var eventHandler = Substitute.For(); testSubject.PropertyChanged += eventHandler; - testSubject.InitializeConnections(connections); testSubject.AddConnection(new Connection(new ConnectionInfo("mySecondOrg", ConnectionServerType.SonarCloud), false)); @@ -107,7 +119,7 @@ public void AddConnection_RaisesEvents() [TestMethod] public void NoConnectionExists_NoConnections_ReturnsTrue() { - testSubject.InitializeConnections([]); + testSubject.InitializeConnections(); testSubject.NoConnectionExists.Should().BeTrue(); } @@ -115,11 +127,54 @@ public void NoConnectionExists_NoConnections_ReturnsTrue() [TestMethod] public void NoConnectionExists_HasConnections_ReturnsFalse() { - testSubject.InitializeConnections(connections); + InitializeTwoConnections(); testSubject.NoConnectionExists.Should().BeFalse(); } + [TestMethod] + public async Task LoadConnectionsWithProgressAsync_InitializesDataAndReportsProgress() + { + await testSubject.LoadConnectionsWithProgressAsync(); + + await progressReporterViewModel.Received(1) + .ExecuteTaskWithProgressAsync( + Arg.Is>(x => + x.TaskToPerform == testSubject.LoadConnectionsAsync && + x.ProgressStatus == UiResources.LoadingConnectionsText && + x.WarningText == UiResources.LoadingConnectionsFailedText)); + } + + [TestMethod] + public async Task LoadConnectionsAsync_LoadsConnections() + { + await testSubject.LoadConnectionsAsync(); + + await threadHandling.Received(1).RunOnUIThreadAsync(Arg.Is(op => op == testSubject.InitializeConnections)); + } + + [TestMethod] + public async Task LoadConnectionsAsync_ConnectionsLoadedSuccessfully_ReturnsTrue() + { + var adapterResponse = await testSubject.LoadConnectionsAsync(); + + adapterResponse.Success.Should().BeTrue(); + } + + [TestMethod] + public async Task LoadConnectionsAsync_LoadingConnectionsThrows_ReturnsFalse() + { + var exceptionMsg = "Failed to load connections"; + var mockedThreadHandling = Substitute.For(); + connectedModeServices.ThreadHandling.Returns(mockedThreadHandling); + mockedThreadHandling.When(x => x.RunOnUIThreadAsync(Arg.Any())).Do(callInfo => throw new Exception(exceptionMsg)); + + var adapterResponse = await testSubject.LoadConnectionsAsync(); + + adapterResponse.Success.Should().BeFalse(); + logger.Received(1).WriteLine(exceptionMsg); + } + private void HasExpectedConnections(IEnumerable expectedConnections) { testSubject.ConnectionViewModels.Should().NotBeNull(); @@ -132,4 +187,22 @@ private void HasExpectedConnections(IEnumerable expectedConnections) connectionViewModel.EnableSmartNotifications.Should().Be(connection.EnableSmartNotifications); } } + + private void InitializeTwoConnections() + { + serverConnectionsRepositoryAdapter.GetAllConnections().Returns(connections); + testSubject.InitializeConnections(); + } + + private void MockServices() + { + serverConnectionsRepositoryAdapter = Substitute.For(); + threadHandling = Substitute.For(); + logger = Substitute.For(); + + connectedModeServices.ServerConnectionsRepositoryAdapter.Returns(serverConnectionsRepositoryAdapter); + connectedModeServices.ThreadHandling.Returns(threadHandling); + connectedModeServices.Logger.Returns(logger); + serverConnectionsRepositoryAdapter.GetAllConnections().Returns([]); + } } diff --git a/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs b/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs index 0f3305a60c..e5a2471ffc 100644 --- a/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs +++ b/src/ConnectedMode.UnitTests/UI/ProgressReporterViewModelTests.cs @@ -127,6 +127,7 @@ public async Task ExecuteTaskWithProgressAsync_TaskWithSuccessResponse_WorkflowI parameters.AfterProgressUpdated(); parameters.TaskToPerform(); parameters.AfterSuccess(Arg.Any()); + parameters.AfterProgressUpdated(); }); testSubject.ProgressStatus.Should().BeNull(); _ = parameters.DidNotReceive().WarningText; @@ -159,6 +160,7 @@ public async Task ExecuteTaskWithProgressAsync_TaskWithFailureResponse_WorkflowI parameters.TaskToPerform(); _ = parameters.WarningText; parameters.AfterFailure(Arg.Any()); + parameters.AfterProgressUpdated(); }); testSubject.Warning.Should().Be(warningText); testSubject.ProgressStatus.Should().BeNull(); diff --git a/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs new file mode 100644 index 0000000000..1e4fce217e --- /dev/null +++ b/src/ConnectedMode/ServerConnectionsRepositoryAdapter.cs @@ -0,0 +1,53 @@ +/* + * SonarLint for Visual Studio + * Copyright (C) 2016-2024 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +using System.ComponentModel.Composition; +using SonarLint.VisualStudio.Core.Binding; + +namespace SonarLint.VisualStudio.ConnectedMode; + +public interface IServerConnectionsRepositoryAdapter +{ + List GetAllConnections(); + List GetAllConnectionsInfo(); +} + +[Export(typeof(IServerConnectionsRepositoryAdapter))] +[method: ImportingConstructor] +internal class ServerConnectionsRepositoryAdapter(IServerConnectionsRepository serverConnectionsRepository) : IServerConnectionsRepositoryAdapter +{ + public List GetAllConnections() + { + var connections = serverConnectionsRepository.GetAll(); + return connections.Select(MapServerConnectionModel).ToList(); + } + + public List GetAllConnectionsInfo() + { + return GetAllConnections().Select(conn => conn.Info).ToList(); + } + + private static Connection MapServerConnectionModel(ServerConnection serverConnection) + { + var serverType = serverConnection is ServerConnection.SonarCloud ? ConnectionServerType.SonarCloud : ConnectionServerType.SonarQube; + var connectionInfo = new ConnectionInfo(serverConnection.Id, serverType); + return new Connection(connectionInfo, serverConnection.Settings.IsSmartNotificationsEnabled); + } +} diff --git a/src/ConnectedMode/UI/ConnectedModeServices.cs b/src/ConnectedMode/UI/ConnectedModeServices.cs index 0aae19a8e6..fa62c5457a 100644 --- a/src/ConnectedMode/UI/ConnectedModeServices.cs +++ b/src/ConnectedMode/UI/ConnectedModeServices.cs @@ -33,6 +33,7 @@ public interface IConnectedModeServices public ILogger Logger { get; } public ISlCoreConnectionAdapter SlCoreConnectionAdapter { get; } public IConfigurationProvider ConfigurationProvider { get; } + public IServerConnectionsRepositoryAdapter ServerConnectionsRepositoryAdapter { get; } } [Export(typeof(IConnectedModeServices))] @@ -43,11 +44,13 @@ public class ConnectedModeServices( IThreadHandling threadHandling, ISlCoreConnectionAdapter slCoreConnectionAdapter, IConfigurationProvider configurationProvider, - ISharedBindingConfigProvider sharedBindingConfigProvider, + ISharedBindingConfigProvider sharedBindingConfigProvider, + IServerConnectionsRepositoryAdapter serverConnectionsRepositoryAdapter, ILogger logger) : IConnectedModeServices { public ISharedBindingConfigProvider SharedBindingConfigProvider { get; } = sharedBindingConfigProvider; + public IServerConnectionsRepositoryAdapter ServerConnectionsRepositoryAdapter { get; } = serverConnectionsRepositoryAdapter; public IBrowserService BrowserService { get; } = browserService; public IThreadHandling ThreadHandling { get; } = threadHandling; public ILogger Logger { get; } = logger; diff --git a/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml b/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml index 4e023c82e3..0323f73765 100644 --- a/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml +++ b/src/ConnectedMode/UI/ManageBinding/ManageBindingDialog.xaml @@ -132,7 +132,7 @@ - - @@ -103,12 +104,16 @@ - - -