From 97546b3eec7462dfd32cb10b0c4cbc35079ebe6b Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh <117642191+georgii-borovinskikh-sonarsource@users.noreply.github.com> Date: Thu, 14 Nov 2024 11:37:11 +0100 Subject: [PATCH] SLVS-1592 Update TaintStore with taints from SLCore (#5821) --- src/Core/ThreadHandlingExtensions.cs | 6 +- .../Taint/TaintIssuesBindingMonitorTests.cs | 107 -- ...intIssuesConfigurationScopeMonitorTests.cs | 63 ++ .../Taint/TaintIssuesSynchronizerTests.cs | 928 +++++++----------- .../TaintIssuesControlViewModelTests.cs | 49 +- .../Taint/TaintStoreTests.cs | 554 +++++------ .../Taint/TaintIssuesBindingMonitor.cs | 86 -- .../TaintIssuesConfigurationScopeMonitor.cs | 56 ++ .../Taint/TaintIssuesSynchronizer.cs | 276 +++--- .../ViewModels/TaintIssuesControlViewModel.cs | 4 +- .../Taint/TaintResources.Designer.cs | 52 +- .../Taint/TaintResources.resx | 10 +- src/IssueViz.Security/Taint/TaintStore.cs | 70 +- .../Taint/TaintSyncPackage.cs | 3 +- 14 files changed, 945 insertions(+), 1319 deletions(-) delete mode 100644 src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs create mode 100644 src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs delete mode 100644 src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs create mode 100644 src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs diff --git a/src/Core/ThreadHandlingExtensions.cs b/src/Core/ThreadHandlingExtensions.cs index 1ebb7feaa5..a704257eab 100644 --- a/src/Core/ThreadHandlingExtensions.cs +++ b/src/Core/ThreadHandlingExtensions.cs @@ -27,10 +27,10 @@ public static Task RunOnBackgroundThread(this IThreadHandling threadHandling, Ac syncMethod(); return Task.CompletedTask; }); - - public static Task RunOnBackgroundThread(this IThreadHandling threadHandling, Func asyncMethod) => Task.FromResult(threadHandling.RunOnBackgroundThread(async () => + + public static Task RunOnBackgroundThread(this IThreadHandling threadHandling, Func asyncMethod) => threadHandling.RunOnBackgroundThread(async () => { await asyncMethod(); return 0; - })); + }); } diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs deleted file mode 100644 index aed1f4acc5..0000000000 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesBindingMonitorTests.cs +++ /dev/null @@ -1,107 +0,0 @@ -/* - * 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; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; -using SonarLint.VisualStudio.TestInfrastructure; -using SonarLint.VisualStudio.IssueVisualization.Security.Taint; - -namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint -{ - [TestClass] - public class TaintIssuesBindingMonitorTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void Ctor_SubscribeToSolutionBindingUpdated() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - synchronizer.Invocations.Clear(); - - activeSolutionBoundTracker.Raise(x=> x.SolutionBindingUpdated += null, EventArgs.Empty); - - synchronizer.Verify(x=> x.SynchronizeWithServer(), Times.Once); - } - - [TestMethod] - public void Ctor_SubscribeToSolutionBindingChanged() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - synchronizer.Invocations.Clear(); - - activeSolutionBoundTracker.Raise(x => x.SolutionBindingChanged += null, new ActiveSolutionBindingEventArgs(BindingConfiguration.Standalone)); - - synchronizer.Verify(x => x.SynchronizeWithServer(), Times.Once); - } - - [TestMethod] - public void Ctor_SubscribeToFolderInitialized() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - synchronizer.Invocations.Clear(); - - folderWorkspaceInitialized.Raise(x => x.FolderWorkspaceInitialized += null, EventArgs.Empty); - - synchronizer.Verify(x => x.SynchronizeWithServer(), Times.Once); - } - - [TestMethod] - public void Dispose_UnsubscribeFromEvents() - { - var synchronizer = new Mock(); - var activeSolutionBoundTracker = new Mock(); - var folderWorkspaceInitialized = new Mock(); - - var testSubject = new TaintIssuesBindingMonitor(activeSolutionBoundTracker.Object, folderWorkspaceInitialized.Object, synchronizer.Object); - testSubject.Dispose(); - synchronizer.Invocations.Clear(); - - folderWorkspaceInitialized.Raise(x => x.FolderWorkspaceInitialized += null, EventArgs.Empty); - activeSolutionBoundTracker.Raise(x => x.SolutionBindingUpdated += null, EventArgs.Empty); - activeSolutionBoundTracker.Raise(x => x.SolutionBindingChanged += null, new ActiveSolutionBindingEventArgs(BindingConfiguration.Standalone)); - - synchronizer.Invocations.Count.Should().Be(0); - } - } -} diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs new file mode 100644 index 0000000000..04b0d5fe97 --- /dev/null +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs @@ -0,0 +1,63 @@ +/* + * 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.IssueVisualization.Security.Taint; +using SonarLint.VisualStudio.SLCore.State; + +namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint; + +[TestClass] +public class TaintIssuesConfigurationScopeMonitorTests +{ + private TaintIssuesConfigurationScopeMonitor testSubject; + private IActiveConfigScopeTracker activeConfigScopeTracker; + private ITaintIssuesSynchronizer taintIssuesSynchronizer; + + [TestInitialize] + public void TestInitialize() + { + activeConfigScopeTracker = Substitute.For(); + taintIssuesSynchronizer = Substitute.For(); + testSubject = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, taintIssuesSynchronizer); + } + + [TestMethod] + public void Ctor_SubscribesToConfigurationScopeEvents() => + activeConfigScopeTracker.Received().CurrentConfigurationScopeChanged += Arg.Any(); + + [TestMethod] + public void Dispose_UnsubscribesToConfigurationScopeEvents() + { + testSubject.Dispose(); + + activeConfigScopeTracker.Received().CurrentConfigurationScopeChanged -= Arg.Any(); + } + + [TestMethod] + public void ConfigScopeChangedEvent_CallsTaintSynchronizer() + { + var configurationScope = new ConfigurationScope("config scope"); + activeConfigScopeTracker.Current.Returns(configurationScope); + + activeConfigScopeTracker.CurrentConfigurationScopeChanged += Raise.Event(); + + taintIssuesSynchronizer.Received(1).UpdateTaintVulnerabilitiesAsync(configurationScope); + } +} diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs index aebed2311b..e31de0d40c 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs @@ -1,587 +1,341 @@ -// /* -// * 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; -// using System.Linq; -// using System.Threading; -// using FluentAssertions; -// using Microsoft.VisualStudio.Shell; -// using Microsoft.VisualStudio.Shell.Interop; -// using Microsoft.VisualStudio.TestTools.UnitTesting; -// using Moq; -// using SonarLint.VisualStudio.Core; -// using SonarLint.VisualStudio.Core.Binding; -// using SonarLint.VisualStudio.Infrastructure.VS; -// using SonarLint.VisualStudio.IssueVisualization.Models; -// using SonarLint.VisualStudio.IssueVisualization.Security.Taint; -// using SonarLint.VisualStudio.IssueVisualization.Security.Taint.TaintList; -// using SonarLint.VisualStudio.TestInfrastructure; -// using SonarQube.Client; -// using SonarQube.Client.Models; -// using Task = System.Threading.Tasks.Task; -// // todo https://sonarsource.atlassian.net/browse/SLVS-1592 -// using VSShellInterop = Microsoft.VisualStudio.Shell.Interop; -// -// namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint -// { -// [TestClass] -// public class TaintIssuesSynchronizerTests -// { -// private static readonly BindingConfiguration BindingConfig_Standalone = BindingConfiguration.Standalone; -// private static readonly BindingConfiguration BindingConfig_Connected = CreateBindingConfig(SonarLintMode.Connected, "any project key"); -// -// [TestMethod] -// public void MefCtor_CheckIsExported() -// { -// MefTestHelpers.CheckTypeCanBeImported( -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// // The constructor calls the service provider so we need to pass a correctly-configured one -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport(), -// MefTestHelpers.CreateExport()); -// } -// -// [TestMethod] -// public void MefCtor_DoesNotCallAnyServices() -// { -// var taintStore = new Mock(); -// var sonarQubeService = new Mock(); -// var taintIssueToIssueVisualizationConverter = new Mock(); -// var configurationProvider = new Mock(); -// var statefulServerBranchProvider = new Mock(); -// var vsUIServiceOperation = new Mock(); -// var toolWindowService = new Mock(); -// var logger = new Mock(); -// -// _ = new TaintIssuesSynchronizer(taintStore.Object, sonarQubeService.Object, taintIssueToIssueVisualizationConverter.Object, configurationProvider.Object, -// toolWindowService.Object, statefulServerBranchProvider.Object, vsUIServiceOperation.Object, logger.Object); -// -// // The MEF constructor should be free-threaded, which it will be if -// // it doesn't make any external calls. -// taintStore.Invocations.Should().BeEmpty(); -// sonarQubeService.Invocations.Should().BeEmpty(); -// taintIssueToIssueVisualizationConverter.Invocations.Should().BeEmpty(); -// configurationProvider.Invocations.Should().BeEmpty(); -// toolWindowService.Invocations.Should().BeEmpty(); -// statefulServerBranchProvider.Invocations.Should().BeEmpty(); -// vsUIServiceOperation.Invocations.Should().BeEmpty(); -// logger.Invocations.Should().BeEmpty(); -// } -// -// [TestMethod] -// public async Task SynchronizeWithServer_NonCriticalException_UIContextAndStoreCleared() -// { -// var logger = new TestLogger(); -// -// var sonarServer = CreateSonarService(); -// sonarServer.Setup(x => x.GetTaintVulnerabilitiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) -// .Throws(new Exception("this is a test")); -// -// var taintStore = new Mock(); -// const uint cookie = 123; -// var monitor = CreateMonitorSelectionMock(cookie); -// -// var testSubject = CreateTestSubject( -// bindingConfig: BindingConfig_Connected, -// sonarService: sonarServer.Object, -// taintStore: taintStore.Object, -// vsMonitor: monitor.Object, -// logger: logger); -// -// Func act = testSubject.SynchronizeWithServer; -// await act.Should().NotThrowAsync(); -// -// CheckStoreIsCleared(taintStore); -// CheckUIContextIsCleared(monitor, cookie); -// logger.AssertPartialOutputStringExists("this is a test"); -// } -// -// [TestMethod] -// public async Task SynchronizeWithServer_CriticalException_ExceptionNotCaught() -// { -// var sonarServer = CreateSonarService(); -// sonarServer.Setup(x => x.GetTaintVulnerabilitiesAsync(It.IsAny(), It.IsAny(), It.IsAny())) -// .Throws(new StackOverflowException()); -// -// var testSubject = CreateTestSubject(sonarService: sonarServer.Object); -// -// Func act = testSubject.SynchronizeWithServer; -// await act.Should().ThrowAsync(); -// } -// -// [TestMethod] -// [Description("Regression test for https://github.com/SonarSource/sonarlint-visualstudio/issues/3152")] -// public void SynchronizeWithServer_DisconnectedInTheMiddle_ServerInfoIsReusedAndNoExceptions() -// { -// var sonarQubeServer = new Mock(); -// sonarQubeServer -// .SetupSequence(x => x.GetServerInfo()) -// .Returns(new ServerInfo(new Version(1, 1), ServerType.SonarQube)) -// .Returns((ServerInfo)null); -// -// var logger = new TestLogger(); -// -// var testSubject = CreateTestSubject( -// bindingConfig: BindingConfig_Connected, -// sonarService: sonarQubeServer.Object, -// logger: logger); -// -// Func act = testSubject.SynchronizeWithServer; -// -// act.Should().NotThrow(); -// -// logger.AssertPartialOutputStringDoesNotExist("NullReferenceException"); -// } -// -// [TestMethod] -// public async Task SynchronizeWithServer_StandaloneMode_StoreAndUIContextCleared() -// { -// var sonarQubeServer = new Mock(); -// var converter = new Mock(); -// var taintStore = new Mock(); -// var serverBranchProvider = new Mock(); -// var logger = new TestLogger(); -// -// const uint cookie = 123; -// var monitor = CreateMonitorSelectionMock(cookie); -// var toolWindowService = new Mock(); -// -// var testSubject = CreateTestSubject( -// bindingConfig: BindingConfig_Standalone, -// taintStore: taintStore.Object, -// sonarService: sonarQubeServer.Object, -// converter: converter.Object, -// serverBranchProvider: serverBranchProvider.Object, -// vsMonitor: monitor.Object, -// toolWindowService: toolWindowService.Object, -// logger: logger); -// -// await testSubject.SynchronizeWithServer(); -// -// CheckStoreIsCleared(taintStore); -// CheckUIContextIsCleared(monitor, cookie); -// logger.AssertPartialOutputStringExists("not in connected mode"); -// -// // Server components should not be called -// sonarQubeServer.Invocations.Should().HaveCount(0); -// converter.Invocations.Should().HaveCount(0); -// serverBranchProvider.Invocations.Should().HaveCount(0); -// toolWindowService.Invocations.Should().HaveCount(0); -// } -// -// [TestMethod] -// public async Task SynchronizeWithServer_SonarQubeServerNotYetConnected_StoreAndUIContextCleared() -// { -// var sonarService = CreateSonarService(isConnected: false); -// var converter = new Mock(); -// var taintStore = new Mock(); -// var logger = new TestLogger(); -// -// const uint cookie = 999; -// var monitor = CreateMonitorSelectionMock(cookie); -// var toolWindowService = new Mock(); -// -// var testSubject = CreateTestSubject( -// bindingConfig: BindingConfig_Connected, -// taintStore: taintStore.Object, -// converter: converter.Object, -// sonarService: sonarService.Object, -// vsMonitor: monitor.Object, -// toolWindowService: toolWindowService.Object, -// logger: logger); -// -// await testSubject.SynchronizeWithServer(); -// -// logger.AssertPartialOutputStringExists("not yet established"); -// CheckConnectedStatusIsChecked(sonarService); -// CheckIssuesAreNotFetched(sonarService); -// -// CheckStoreIsCleared(taintStore); -// CheckUIContextIsCleared(monitor, cookie); -// -// // Should be nothing to convert or display in the tool window -// converter.Invocations.Should().HaveCount(0); -// toolWindowService.Invocations.Should().HaveCount(0); -// } -// -// [TestMethod] -// [DataRow("7.9")] -// [DataRow("8.5.9.9")] -// public async Task SynchronizeWithServer_UnsupportedServerVersion_StoreAndUIContextCleared(string versionString) -// { -// var sonarQubeServer = CreateSonarService(isConnected: true, serverType: ServerType.SonarQube, versionString); -// var logger = new TestLogger(); -// -// const uint cookie = 999; -// var monitor = CreateMonitorSelectionMock(cookie); -// var taintStore = new Mock(); -// -// var testSubject = CreateTestSubject( -// bindingConfig: BindingConfig_Connected, -// taintStore: taintStore.Object, -// sonarService: sonarQubeServer.Object, -// vsMonitor: monitor.Object, -// logger: logger); -// -// await testSubject.SynchronizeWithServer(); -// -// logger.AssertPartialOutputStringExists("requires SonarQube Server v8.6 or later"); -// logger.AssertPartialOutputStringExists($"Connected SonarQube Server version: v{versionString}"); -// -// CheckIssuesAreNotFetched(sonarQubeServer); -// CheckStoreIsCleared(taintStore); -// CheckUIContextIsCleared(monitor, cookie); -// } -// -// [TestMethod] -// [DataRow(ServerType.SonarCloud, "0.1")] -// [DataRow(ServerType.SonarQube, "8.6.0.0")] -// [DataRow(ServerType.SonarQube, "9.9")] -// public async Task SynchronizeWithServer_SupportedServer_IssuesFetched(ServerType serverType, string serverVersion) -// { -// var logger = new TestLogger(); -// var sonarServer = CreateSonarService(isConnected: true, serverType, serverVersion); -// -// var bindingConfig = CreateBindingConfig(SonarLintMode.Connected, "keyXXX"); -// var serverBranchProvider = CreateServerBranchProvider("branchXXX"); -// SetupTaintIssues(sonarServer, "keyXXX", "branchXXX"); -// -// var testSubject = CreateTestSubject( -// bindingConfig: bindingConfig, -// serverBranchProvider: serverBranchProvider.Object, -// sonarService: sonarServer.Object, -// logger: logger); -// -// await testSubject.SynchronizeWithServer(); -// -// logger.AssertPartialOutputStringDoesNotExist("requires SonarQube v8.6 or later"); -// CheckIssuesAreFetched(sonarServer, "keyXXX", "branchXXX"); -// } -// -// [TestMethod] -// [DataRow(SonarLintMode.Connected)] -// [DataRow(SonarLintMode.LegacyConnected)] -// public async Task SynchronizeWithServer_ConnectedModeWithNoIssues_StoreIsSetAndUIContextCleared(SonarLintMode sonarLintMode) -// { -// var sonarService = CreateSonarService(isConnected: true); -// var bindingConfig = CreateBindingConfig(sonarLintMode, "my-project-key"); -// var serverBranchProvider = CreateServerBranchProvider("my-branch"); -// var analysisInformation = new AnalysisInformation("my-branch", DateTimeOffset.Now); -// -// SetupTaintIssues(sonarService, "my-project-key", "my-branch" /* no issues */); -// SetupAnalysisInformation(sonarService, "my-project-key", analysisInformation); -// -// var taintStore = new Mock(); -// var converter = new Mock(); -// -// const uint cookie = 999; -// var monitor = CreateMonitorSelectionMock(cookie); -// var toolWindowService = new Mock(); -// -// var testSubject = CreateTestSubject( -// bindingConfig: bindingConfig, -// taintStore: taintStore.Object, -// converter: converter.Object, -// sonarService: sonarService.Object, -// serverBranchProvider: serverBranchProvider.Object, -// vsMonitor: monitor.Object, -// toolWindowService: toolWindowService.Object); -// -// await testSubject.SynchronizeWithServer(); -// -// CheckConnectedStatusIsChecked(sonarService); -// CheckIssuesAreFetched(sonarService, "my-project-key", "my-branch"); -// CheckUIContextIsCleared(monitor, cookie); -// -// taintStore.Verify(x => x.Set(Enumerable.Empty(), -// It.Is((AnalysisInformation a) => -// a.AnalysisTimestamp == analysisInformation.AnalysisTimestamp && -// a.BranchName == analysisInformation.BranchName)), Times.Once); -// -// // Should be nothing to display in the tool window -// toolWindowService.Invocations.Should().HaveCount(0); -// } -// -// [TestMethod] -// [DataRow(SonarLintMode.Connected)] -// [DataRow(SonarLintMode.LegacyConnected)] -// public async Task SynchronizeWithServer_ConnectedMode_UsesExpectedBranch(SonarLintMode sonarLintMode) -// { -// var bindingConfig = CreateBindingConfig(sonarLintMode, "xxx_project-key"); -// -// var serverBranchProvider = CreateServerBranchProvider("branch-XYZ"); -// -// var sonarQubeService = CreateSonarService(isConnected: true); -// SetupTaintIssues(sonarQubeService, "xxx_project-key", "branch-XYZ"); -// -// var testSubject = CreateTestSubject( -// bindingConfig: bindingConfig, -// sonarService: sonarQubeService.Object, -// serverBranchProvider: serverBranchProvider.Object); -// -// await testSubject.SynchronizeWithServer(); -// -// serverBranchProvider.VerifyAll(); -// sonarQubeService.Verify(x => x.GetTaintVulnerabilitiesAsync("xxx_project-key", "branch-XYZ", It.IsAny()), -// Times.Once()); -// } -// -// [TestMethod] -// [DataRow(SonarLintMode.Connected)] -// [DataRow(SonarLintMode.LegacyConnected)] -// public async Task SynchronizeWithServer_ConnectedModeWithIssues_IssuesAddedToStore(SonarLintMode sonarLintMode) -// { -// var serverIssue1 = new TestSonarQubeIssue(); -// var serverIssue2 = new TestSonarQubeIssue(); -// var issueViz1 = Mock.Of(); -// var issueViz2 = Mock.Of(); -// -// var converter = new Mock(); -// converter.Setup(x => x.Convert(serverIssue1)).Returns(issueViz1); -// converter.Setup(x => x.Convert(serverIssue2)).Returns(issueViz2); -// -// var taintStore = new Mock(); -// -// var analysisInformation = new AnalysisInformation("a branch", DateTimeOffset.Now); -// -// var sonarServer = CreateSonarService(); -// -// var serverBranchProvider = CreateServerBranchProvider("a branch"); -// var bindingConfig = CreateBindingConfig(sonarLintMode, "projectKey123"); -// SetupTaintIssues(sonarServer, "projectKey123", "a branch", serverIssue1, serverIssue2); -// SetupAnalysisInformation(sonarServer, "projectKey123", analysisInformation); -// -// var testSubject = CreateTestSubject( -// bindingConfig: bindingConfig, -// taintStore: taintStore.Object, -// converter: converter.Object, -// sonarService: sonarServer.Object, -// serverBranchProvider: serverBranchProvider.Object); -// -// await testSubject.SynchronizeWithServer(); -// -// taintStore.Verify(x => x.Set(new[] { issueViz1, issueViz2 }, -// It.Is((AnalysisInformation a) => -// a.AnalysisTimestamp == analysisInformation.AnalysisTimestamp && -// a.BranchName == analysisInformation.BranchName)), Times.Once); -// } -// -// [TestMethod] -// [DataRow(SonarLintMode.Connected)] -// [DataRow(SonarLintMode.LegacyConnected)] -// public async Task SynchronizeWithServer_ConnectedModeWithIssues_UIContextIsSetAndToolWindowCalled(SonarLintMode sonarLintMode) -// { -// var sonarService = CreateSonarService(); -// -// var bindingConfig = CreateBindingConfig(sonarLintMode, "myProjectKey___"); -// var serverBranchProvider = CreateServerBranchProvider("branchYYY"); -// SetupTaintIssues(sonarService, "myProjectKey___", "branchYYY", new TestSonarQubeIssue()); -// SetupAnalysisInformation(sonarService, "myProjectKey___", new AnalysisInformation("branchYYY", DateTimeOffset.Now)); -// -// const uint cookie = 212; -// var monitor = CreateMonitorSelectionMock(cookie); -// var toolWindowService = new Mock(); -// -// var testSubject = CreateTestSubject( -// bindingConfig: bindingConfig, -// serverBranchProvider: serverBranchProvider.Object, -// sonarService: sonarService.Object, -// vsMonitor: monitor.Object, -// toolWindowService: toolWindowService.Object); -// -// await testSubject.SynchronizeWithServer(); -// -// CheckConnectedStatusIsChecked(sonarService); -// CheckIssuesAreFetched(sonarService, "myProjectKey___", "branchYYY"); -// CheckUIContextIsSet(monitor, cookie); -// CheckToolWindowServiceIsCalled(toolWindowService); -// } -// -// [TestMethod] -// [DataRow(null)] -// [DataRow("")] -// [DataRow("unknown local branch")] -// public async Task SynchronizeWithServer_NoMatchingServerBranch_UIContextAndStoreCleared(string localBranch) -// { -// var sonarService = CreateSonarService(); -// var bindingConfig = CreateBindingConfig(SonarLintMode.Connected, "my proj"); -// var serverBranchProvider = CreateServerBranchProvider(localBranch); -// SetupTaintIssues(sonarService, "my proj", localBranch, new TestSonarQubeIssue()); -// -// var analysisInformation = new AnalysisInformation("some main branch", DateTimeOffset.Now); -// SetupAnalysisInformation(sonarService, "my proj", analysisInformation); -// -// const uint cookie = 212; -// var monitor = CreateMonitorSelectionMock(cookie); -// var toolWindowService = new Mock(); -// var taintStore = new Mock(); -// -// var testSubject = CreateTestSubject( -// taintStore: taintStore.Object, -// bindingConfig: bindingConfig, -// serverBranchProvider: serverBranchProvider.Object, -// sonarService: sonarService.Object, -// vsMonitor: monitor.Object, -// toolWindowService: toolWindowService.Object); -// -// using (new AssertIgnoreScope()) -// { -// await testSubject.SynchronizeWithServer(); -// } -// -// CheckIssuesAreFetched(sonarService, "my proj", localBranch); -// CheckUIContextIsCleared(monitor, cookie); -// CheckStoreIsCleared(taintStore); -// } -// -// private static BindingConfiguration CreateBindingConfig(SonarLintMode mode = SonarLintMode.Connected, string projectKey = "any") -// => new(new BoundServerProject("solution", projectKey, new ServerConnection.SonarQube(new Uri("http://bound"))), mode, "any dir"); -// -// private static TaintIssuesSynchronizer CreateTestSubject( -// BindingConfiguration bindingConfig = null, -// ITaintStore taintStore = null, -// ITaintIssueToIssueVisualizationConverter converter = null, -// ILogger logger = null, -// ISonarQubeService sonarService = null, -// IStatefulServerBranchProvider serverBranchProvider = null, -// IVsMonitorSelection vsMonitor = null, -// IToolWindowService toolWindowService = null) -// { -// taintStore ??= Mock.Of(); -// converter ??= Mock.Of(); -// -// var serviceOperation = CreateServiceOperation(vsMonitor); -// -// bindingConfig ??= CreateBindingConfig(SonarLintMode.Connected, "any branch"); -// -// var configurationProvider = new Mock(); -// configurationProvider -// .Setup(x => x.GetConfiguration()) -// .Returns(bindingConfig); -// -// sonarService ??= CreateSonarService().Object; -// serverBranchProvider ??= Mock.Of(); -// toolWindowService ??= Mock.Of(); -// -// logger ??= Mock.Of(); -// -// return new TaintIssuesSynchronizer(taintStore, sonarService, converter, configurationProvider.Object, -// toolWindowService, serverBranchProvider, serviceOperation, logger); -// } -// -// private static IVsUIServiceOperation CreateServiceOperation(IVsMonitorSelection svcToPassToCallback) -// { -// svcToPassToCallback ??= Mock.Of(); -// -// var serviceOp = new Mock(); -// -// // Set up the mock to invoke the operation with the supplied VS service -// serviceOp.Setup(x => x.Execute(It.IsAny>())) -// .Callback>(op => op(svcToPassToCallback)); -// -// return serviceOp.Object; -// } -// -// private static Mock CreateServerBranchProvider(string branchName) -// { -// var serverBranchProvider = new Mock(); -// serverBranchProvider.Setup(x => x.GetServerBranchNameAsync(It.IsAny())).ReturnsAsync(branchName); -// return serverBranchProvider; -// } -// -// private static Mock CreateSonarService(bool isConnected = true, -// ServerType serverType = ServerType.SonarQube, -// string versionString = "9.9") -// { -// var serverInfo = isConnected ? new ServerInfo(new Version(versionString), serverType) : null; -// -// var sonarQubeService = new Mock(); -// sonarQubeService.Setup(x => x.GetServerInfo()).Returns(serverInfo); -// -// return sonarQubeService; -// } -// -// private static Mock CreateMonitorSelectionMock(uint cookie) -// { -// var monitor = new Mock(); -// var localGuid = TaintIssuesExistUIContext.Guid; -// monitor.Setup(x => x.GetCmdUIContextCookie(ref localGuid, out cookie)); -// -// return monitor; -// } -// -// private static void CheckUIContextIsCleared(Mock monitorMock, uint expectedCookie) => -// CheckUIContextUpdated(monitorMock, expectedCookie, 0); -// -// private static void CheckUIContextIsSet(Mock monitorMock, uint expectedCookie) => -// CheckUIContextUpdated(monitorMock, expectedCookie, 1); -// -// private static void CheckUIContextUpdated(Mock monitorMock, uint expectedCookie, int expectedState) => -// monitorMock.Verify(x => x.SetCmdUIContext(expectedCookie, expectedState), Times.Once); -// -// private static void CheckConnectedStatusIsChecked(Mock serviceMock) => -// serviceMock.Verify(x => x.GetServerInfo(), Times.Once); -// -// private static void CheckIssuesAreFetched(Mock serviceMock, string projectKey, string branch) => -// serviceMock.Verify(x => x.GetTaintVulnerabilitiesAsync(projectKey, branch, It.IsAny()), Times.Once); -// -// private static void CheckIssuesAreNotFetched(Mock serviceMock) => -// serviceMock.Verify(x => x.GetTaintVulnerabilitiesAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); -// -// private static void CheckToolWindowServiceIsCalled(Mock toolWindowServiceMock) => -// toolWindowServiceMock.Verify(x => x.EnsureToolWindowExists(TaintToolWindow.ToolWindowId), Times.Once); -// -// private void CheckStoreIsCleared(Mock taintStore) => -// taintStore.Verify(x => x.Set(Enumerable.Empty(), null), Times.Once()); -// -// private class TestSonarQubeIssue : SonarQubeIssue -// { -// public TestSonarQubeIssue() -// : base("test", "test", "test", "test", "test", "test", true, SonarQubeIssueSeverity.Info, -// DateTimeOffset.MinValue, DateTimeOffset.MinValue, null, null) -// { -// } -// } -// -// private void SetupAnalysisInformation(Mock sonarQubeService, string projectKey, AnalysisInformation mainBranchInformation) -// { -// var projectBranches = new[] -// { -// new SonarQubeProjectBranch(Guid.NewGuid().ToString(), false, DateTimeOffset.MaxValue, "BRANCH"), -// new SonarQubeProjectBranch(mainBranchInformation.BranchName, true, mainBranchInformation.AnalysisTimestamp, "BRANCH"), -// new SonarQubeProjectBranch(Guid.NewGuid().ToString(), false, DateTimeOffset.MinValue, "BRANCH") -// }; -// -// sonarQubeService.Setup(x => x.GetProjectBranchesAsync(projectKey, CancellationToken.None)) -// .ReturnsAsync(projectBranches); -// } -// -// private void SetupTaintIssues(Mock sonarQubeService, string projectKey, string branch, params SonarQubeIssue[] issues) -// { -// sonarQubeService -// .Setup(x => x.GetTaintVulnerabilitiesAsync(projectKey, branch, CancellationToken.None)) -// .ReturnsAsync(issues); -// } -// } -// } +/* + * 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 Microsoft.VisualStudio; +using Microsoft.VisualStudio.Shell.Interop; +using NSubstitute.ClearExtensions; +using NSubstitute.ExceptionExtensions; +using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Synchronization; +using SonarLint.VisualStudio.Infrastructure.VS; +using SonarLint.VisualStudio.IssueVisualization.Models; +using SonarLint.VisualStudio.IssueVisualization.Security.Taint; +using SonarLint.VisualStudio.IssueVisualization.Security.Taint.TaintList; +using SonarLint.VisualStudio.SLCore.Common.Models; +using SonarLint.VisualStudio.SLCore.Core; +using SonarLint.VisualStudio.SLCore.Service.Rules.Models; +using SonarLint.VisualStudio.SLCore.Service.Taint; +using SonarLint.VisualStudio.SLCore.State; +using SonarLint.VisualStudio.TestInfrastructure; + +namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint; + +[TestClass] +public class TaintIssuesSynchronizerTests +{ + private ITaintStore taintStore; + private ISLCoreServiceProvider slCoreServiceProvider; + private ITaintVulnerabilityTrackingSlCoreService taintService; + private ITaintIssueToIssueVisualizationConverter converter; + private IToolWindowService toolWindowService; + private IVsUIServiceOperation vsUiServiceOperation; + private IVsMonitorSelection vsMonitorSelection; + private IThreadHandling threadHandling; + private IAsyncLockFactory asyncLockFactory; + private IAsyncLock asyncLock; + private TestLogger logger; + private TaintIssuesSynchronizer testSubject; + private IReleaseAsyncLock asyncLockReleaser; + private static readonly ConfigurationScope None = null; + private static readonly ConfigurationScope Standalone = new("some id 1", null, null, "some path"); + private static readonly ConfigurationScope ConnectedWithUninitializedRoot = new("some id 2", "some connection", "some project"); + private static readonly ConfigurationScope ConnectedReady = new("some id 3", "some connection", "some project", "some path"); + + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); + + [TestMethod] + public void Ctor_DoesNotCallAnyServices() + { + asyncLockFactory.ClearSubstitute(); + var loggerMock = Substitute.For(); + // The MEF constructor should be free-threaded, which it will be if + // it doesn't make any external calls. AsyncLockFactory is free-threaded, calling it is allowed + testSubject = new TaintIssuesSynchronizer(taintStore, + slCoreServiceProvider, + converter, + toolWindowService, + vsUiServiceOperation, + threadHandling, + asyncLockFactory, + loggerMock); + taintStore.ReceivedCalls().Should().BeEmpty(); + slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); + converter.ReceivedCalls().Should().BeEmpty(); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + vsUiServiceOperation.ReceivedCalls().Should().BeEmpty(); + threadHandling.ReceivedCalls().Should().BeEmpty(); + asyncLockFactory.Received(1).Create(); + loggerMock.ReceivedCalls().Should().BeEmpty(); + } + + [TestInitialize] + public void TestInitialize() + { + taintStore = Substitute.For(); + taintService = Substitute.For(); + slCoreServiceProvider = CreateDefaultServiceProvider(taintService); + converter = Substitute.For(); + toolWindowService = Substitute.For(); + vsMonitorSelection = Substitute.For(); + vsUiServiceOperation = CreateDefaultServiceOperation(vsMonitorSelection); + threadHandling = CreateDefaultThreadHandling(); + asyncLock = Substitute.For(); + asyncLockReleaser = Substitute.For(); + asyncLockFactory = CreateDefaultAsyncLockFactory(asyncLock, asyncLockReleaser); + logger = new TestLogger(); + testSubject = new TaintIssuesSynchronizer(taintStore, + slCoreServiceProvider, + converter, + toolWindowService, + vsUiServiceOperation, + threadHandling, + asyncLockFactory, + logger); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_NonCriticalException_UIContextAndStoreCleared() + { + slCoreServiceProvider.TryGetTransientService(out Arg.Any()).Throws(new Exception("this is a test")); + + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); + + var act = () => testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); + + await act.Should().NotThrowAsync(); + CheckStoreIsCleared(); + CheckUIContextIsCleared(cookie); + logger.AssertPartialOutputStringExists("this is a test"); + asyncLockReleaser.Received().Dispose(); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_CriticalException_ExceptionNotCaught() + { + slCoreServiceProvider.TryGetTransientService(out Arg.Any()).Throws(new DivideByZeroException()); + + var act = async () => await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); + + await act.Should().ThrowAsync(); + asyncLockReleaser.Received().Dispose(); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_NoConfigurationScope_StoreAndUIContextCleared() + { + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); + + await testSubject.UpdateTaintVulnerabilitiesAsync(None); + + CheckStoreIsCleared(); + CheckUIContextIsCleared(cookie); + logger.AssertPartialOutputStringExists("not in connected mode"); + slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); + converter.ReceivedCalls().Should().BeEmpty(); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_StandaloneMode_StoreAndUIContextCleared() + { + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); + + await testSubject.UpdateTaintVulnerabilitiesAsync(Standalone); + + CheckStoreIsCleared(); + CheckUIContextIsCleared(cookie); + logger.AssertPartialOutputStringExists("not in connected mode"); + slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_ConnectedModeConfigScope_NotReady_StoreAndUIContextCleared() + { + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); + + await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedWithUninitializedRoot); + + CheckStoreIsCleared(); + CheckUIContextIsCleared(cookie); + slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_SLCoreNotInitialized_StoreAndUIContextCleared() + { + slCoreServiceProvider.TryGetTransientService(out ITaintVulnerabilityTrackingSlCoreService _).Returns(false); + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); + + await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); + + CheckStoreIsCleared(); + CheckUIContextIsCleared(cookie); + Received.InOrder(() => + { + threadHandling.RunOnBackgroundThread(Arg.Any>>()); + asyncLock.AcquireAsync(); + slCoreServiceProvider.TryGetTransientService(out Arg.Any()); + asyncLockReleaser.Dispose(); + }); + taintService.ReceivedCalls().Should().BeEmpty(); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_TaintStoreAlreadyInitialized_Ignored() + { + taintStore.ConfigurationScope.Returns(ConnectedReady.Id); + + await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); + + taintStore.DidNotReceiveWithAnyArgs().Set(default, default); + taintService.ReceivedCalls().Should().BeEmpty(); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_NoIssuesForConfigScope_SetsStoreAndClearsUIContext() + { + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); + taintService.ListAllAsync(Arg.Is(x => x.shouldRefresh && x.configurationScopeId == ConnectedReady.Id)).Returns(new ListAllTaintsResponse([])); + + await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); + + CheckUIContextIsCleared(cookie); + converter.DidNotReceiveWithAnyArgs().Convert(default, default); + taintStore.Received(1).Set(Arg.Is>(x => x.SequenceEqual(Array.Empty())), ConnectedReady.Id); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + logger.AssertPartialOutputStringExists(string.Format(TaintResources.Synchronizer_NumberOfServerIssues, 0)); + } + + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_MultipleIssues_SetsStoreAndUIContextAndCallsToolWindow() + { + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); + List taints = [CreateDefaultTaintDto(), CreateDefaultTaintDto(), CreateDefaultTaintDto()]; + List taintVisualizations = [Substitute.For(), Substitute.For(), Substitute.For()]; + taintService.ListAllAsync(Arg.Is(x => x.shouldRefresh && x.configurationScopeId == ConnectedReady.Id)).Returns(new ListAllTaintsResponse(taints)); + for (var i = 0; i < taints.Count; i++) + { + converter.Convert(taints[i], ConnectedReady.RootPath).Returns(taintVisualizations[i]); + } + + await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); + + CheckUIContextIsSet(cookie); + converter.ReceivedWithAnyArgs(3).Convert(default, default); + taintStore.Received(1).Set(Arg.Is>(x => x.SequenceEqual(taintVisualizations)), ConnectedReady.Id); + CheckToolWindowServiceIsCalled(); + logger.AssertPartialOutputStringExists(string.Format(TaintResources.Synchronizer_NumberOfServerIssues, 3)); + } + + private void SetUpMonitorSelectionMock(uint cookie) + { + var localGuid = TaintIssuesExistUIContext.Guid; + vsMonitorSelection.GetCmdUIContextCookie(ref localGuid, out Arg.Any()).Returns(call => + { + call[1] = cookie; + return VSConstants.S_OK; + }); + } + + private static ISLCoreServiceProvider CreateDefaultServiceProvider(ITaintVulnerabilityTrackingSlCoreService taintService) + { + var slCoreServiceProvider = Substitute.For(); + slCoreServiceProvider.TryGetTransientService(out ITaintVulnerabilityTrackingSlCoreService _).Returns(call => + { + call[0] = taintService; + return true; + }); + + return slCoreServiceProvider; + } + + private static IAsyncLockFactory CreateDefaultAsyncLockFactory(IAsyncLock asyncLock, IReleaseAsyncLock release) + { + var factory = Substitute.For(); + factory.Create().Returns(asyncLock); + asyncLock.AcquireAsync().Returns(release); + return factory; + } + + private static IVsUIServiceOperation CreateDefaultServiceOperation(IVsMonitorSelection svcToPassToCallback) + { + var serviceOp = Substitute.For(); + + // Set up the mock to invoke the operation with the supplied VS service + serviceOp.When(x => x.Execute(Arg.Any>())) + .Do(call => call.Arg>().Invoke(svcToPassToCallback)); + + return serviceOp; + } + + private static IThreadHandling CreateDefaultThreadHandling() + { + var mock = Substitute.For(); + mock.RunOnBackgroundThread(Arg.Any>>()).Returns(call => call.Arg>>()()); + return mock; + } + + private static TaintVulnerabilityDto CreateDefaultTaintDto() => + new( + Guid.Parse("efa697a2-9cfd-4faf-ba21-71b378667a81"), + "serverkey", + true, + "rulekey:S123", + "message1", + "file\\path\\1", + DateTimeOffset.Now, + new StandardModeDetails(IssueSeverity.MINOR, RuleType.VULNERABILITY), + [], + new TextRangeWithHashDto(1, 2, 3, 4, "hash1"), + "rulecontext", + false); + + private void CheckUIContextIsCleared(uint expectedCookie) => CheckUIContextUpdated(expectedCookie, 0); + + private void CheckUIContextIsSet(uint expectedCookie) => CheckUIContextUpdated(expectedCookie, 1); + + private void CheckUIContextUpdated(uint expectedCookie, int expectedState) => + vsMonitorSelection.Received(1).SetCmdUIContext(expectedCookie, expectedState); + + private void CheckToolWindowServiceIsCalled() => + toolWindowService.Received().EnsureToolWindowExists(TaintToolWindow.ToolWindowId); + + private void CheckStoreIsCleared() => taintStore.Received(1).Set([], null); +} diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs index 95e90b3330..a32de3b9aa 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs @@ -1,4 +1,4 @@ -/* +/* * SonarLint for Visual Studio * Copyright (C) 2016-2024 SonarSource SA * mailto:info AT sonarsource DOT com @@ -694,48 +694,6 @@ public void SetSelectedIssue_ValueIsTheSame_SelectionServiceNotCalled() selectionService.VerifyNoOtherCalls(); } - [TestMethod] - public void AnalysisInformation_NoAnalysisInformation_Null() - { - var store = new Mock(); - SetupAnalysisInformation(store, null); - - var testSubject = CreateTestSubject(store: store); - - testSubject.AnalysisInformation.Should().BeNull(); - } - - [TestMethod] - public void AnalysisInformation_HasAnalysisInformation_PropertySet() - { - var store = new Mock(); - var analysisInformation = new AnalysisInformation("some branch", default); - SetupAnalysisInformation(store, analysisInformation); - - var testSubject = CreateTestSubject(store: store); - - testSubject.AnalysisInformation.Should().BeSameAs(analysisInformation); - } - - [TestMethod] - public void AnalysisInformation_IssuesChanged_RaisesPropertyChanged() - { - var store = new Mock(); - var testSubject = CreateTestSubject(store: store); - - var eventHandler = new Mock(); - testSubject.PropertyChanged += eventHandler.Object; - - var analysisInformation = new AnalysisInformation("some branch", default); - - SetupAnalysisInformation(store, analysisInformation); - RaiseStoreIssuesChangedEvent(store); - - VerifyPropertyChangedWasRaised(eventHandler, nameof(testSubject.AnalysisInformation)); - - testSubject.AnalysisInformation.Should().BeSameAs(analysisInformation); - } - [TestMethod] [DataRow(null, ServerType.SonarCloud, nameof(ServerType.SonarCloud), true)] [DataRow(null, null, "", false)] @@ -1011,10 +969,5 @@ private void VerifyPropertyChangedWasRaised(Mock ev It.Is((PropertyChangedEventArgs e) => e.PropertyName == expectedProperty)), Times.Once); } - - private void SetupAnalysisInformation(Mock store, AnalysisInformation analysisInformation) - { - store.Setup(x => x.GetAnalysisInformation()).Returns(analysisInformation); - } } } diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs index 167bc740ca..bec2492be3 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs @@ -18,374 +18,344 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; -using System.Linq; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using SonarLint.VisualStudio.TestInfrastructure; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; using SonarLint.VisualStudio.IssueVisualization.Security.Taint; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; -namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint +namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint; + +[TestClass] +public class TaintStoreTests { - [TestClass] - public class TaintStoreTests + private ITaintStore testSubject; + + [TestMethod] + public void MefCtor_CheckExports() { - [TestMethod] - public void MefCtor_CheckExports() - { - var batch = new CompositionBatch(); + var batch = new CompositionBatch(); - var storeImport = new SingleObjectImporter(); - var issuesStoreImport = new SingleObjectImporter(); - batch.AddPart(storeImport); - batch.AddPart(issuesStoreImport); + var storeImport = new SingleObjectImporter(); + var issuesStoreImport = new SingleObjectImporter(); + batch.AddPart(storeImport); + batch.AddPart(issuesStoreImport); - var catalog = new TypeCatalog(typeof(TaintStore)); - using var container = new CompositionContainer(catalog); - container.Compose(batch); + var catalog = new TypeCatalog(typeof(TaintStore)); + using var container = new CompositionContainer(catalog); + container.Compose(batch); - storeImport.Import.Should().NotBeNull(); - issuesStoreImport.Import.Should().NotBeNull(); + storeImport.Import.Should().NotBeNull(); + issuesStoreImport.Import.Should().NotBeNull(); - storeImport.Import.Should().BeSameAs(issuesStoreImport.Import); - } + storeImport.Import.Should().BeSameAs(issuesStoreImport.Import); + } - [TestMethod] - public void GetAll_ReturnsImmutableInstance() - { - var testSubject = CreateTestSubject(); - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, new AnalysisInformation("some branch", DateTimeOffset.Now)); + [TestInitialize] + public void TestInitialize() + { + testSubject = new TaintStore(); + } - var issuesList1 = testSubject.GetAll(); - testSubject.Add(SetupIssueViz()); - var issuesList2 = testSubject.GetAll(); + [TestMethod] + public void GetAll_ReturnsImmutableInstance() + { + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "config scope"); - issuesList1.Count.Should().Be(2); - issuesList2.Count.Should().Be(3); - } + var issuesList1 = testSubject.GetAll(); + testSubject.Add(SetupIssueViz()); + var issuesList2 = testSubject.GetAll(); - [TestMethod] - public void Set_NullCollection_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Set(null, null); + issuesList1.Count.Should().Be(2); + issuesList2.Count.Should().Be(3); + } - act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); - } + [TestMethod] + public void Set_NullCollection_ArgumentNullException() + { + Action act = () => testSubject.Set(null, null); - [TestMethod] - public void Set_NoSubscribersToIssuesChangedEvent_NoException() - { - var testSubject = CreateTestSubject(); + act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); + } + + [TestMethod] + public void Set_NoSubscribersToIssuesChangedEvent_NoException() + { + Action act = () => testSubject.Set(new[] { SetupIssueViz() }, "some config scope"); + + act.Should().NotThrow(); + } + + [TestMethod] + public void Set_NoPreviousItems_NoNewItems_CollectionChangedAndEventRaised() + { + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; + + testSubject.Set([], null); + + testSubject.GetAll().Should().BeEmpty(); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + } + + [TestMethod] + public void Set_NoPreviousItems_HasNewItems_CollectionChangedAndEventRaised() + { + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; + + var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(newItems, "some config scope"); + + testSubject.GetAll().Should().BeEquivalentTo(newItems); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + var eventArgs = (IssuesChangedEventArgs)eventHandlerMock.ReceivedCalls().Single().GetArguments()[1]!; + eventArgs.RemovedIssues.Should().BeEmpty(); + eventArgs.AddedIssues.Should().BeEquivalentTo(newItems); + } + + [TestMethod] + public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() + { + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "some config scope"); + + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; + + testSubject.Set([], "some config scope"); + + testSubject.GetAll().Should().BeEmpty(); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + var eventArgs = (IssuesChangedEventArgs)eventHandlerMock.ReceivedCalls().Single().GetArguments()[1]!; + eventArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); + eventArgs.AddedIssues.Should().BeEmpty(); + } - Action act = () => testSubject.Set(new[] { SetupIssueViz() }, null); - - act.Should().NotThrow(); - } + [TestMethod] + public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() + { + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "some config scope"); - [TestMethod] - public void Set_NoPreviousItems_NoNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - testSubject.IssuesChanged += (sender, args) => { callCount++; }; - - testSubject.Set(Enumerable.Empty(), null); - - testSubject.GetAll().Should().BeEmpty(); - callCount.Should().Be(1); - } - - [TestMethod] - public void Set_NoPreviousItems_HasNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; - - var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(newItems, null); - - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEmpty(); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); - } + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - [TestMethod] - public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, null); + var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(newItems, "some config scope"); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + testSubject.GetAll().Should().BeEquivalentTo(newItems); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + var eventArgs = (IssuesChangedEventArgs)eventHandlerMock.ReceivedCalls().Single().GetArguments()[1]!; + eventArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); + eventArgs.AddedIssues.Should().BeEquivalentTo(newItems); + } - testSubject.Set(Enumerable.Empty(), null); + [TestMethod] + public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() + { + var issueViz1 = SetupIssueViz("key1"); + var issueViz2 = SetupIssueViz("key2"); + var issueViz2NewObject = SetupIssueViz("key2"); + var issueViz3 = SetupIssueViz("key3"); - testSubject.GetAll().Should().BeEmpty(); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEmpty(); - } + var oldItems = new[] { issueViz1, issueViz2 }; + testSubject.Set(oldItems, "some config scope"); - [TestMethod] - public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, null); + var newItems = new[] { issueViz2NewObject, issueViz3}; + testSubject.Set(newItems, "some config scope"); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + testSubject.GetAll().Should().BeEquivalentTo(newItems); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + var eventArgs = (IssuesChangedEventArgs)eventHandlerMock.ReceivedCalls().Single().GetArguments()[1]!; + eventArgs.RemovedIssues.Should().BeEquivalentTo(issueViz1); + eventArgs.AddedIssues.Should().BeEquivalentTo(issueViz3); + } - var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(newItems, null); + [TestMethod] + public void Set_HasItems_NoConfigScope_Throws() + { + var issueViz1 = SetupIssueViz("key1"); - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); - } + var act = () => testSubject.Set([issueViz1], null); - [TestMethod] - public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); + act.Should().Throw().And.ParamName.Should().Be("newConfigurationScope"); + } - var issueViz1 = SetupIssueViz("key1"); - var issueViz2 = SetupIssueViz("key2"); - var issueViz2NewObject = SetupIssueViz("key2"); - var issueViz3 = SetupIssueViz("key3"); + [TestMethod] + public void ConfigScope_NoInformation_ReturnsNull() + { + testSubject.Set([], null); - var oldItems = new[] { issueViz1, issueViz2 }; - testSubject.Set(oldItems, null); + var result = testSubject.ConfigurationScope; + result.Should().BeNull(); + } - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + [TestMethod] + public void ConfigScope_HasInformation_ReturnsInformation() + { + const string newConfigurationScope = "some config scope"; - var newItems = new[] { issueViz2NewObject, issueViz3}; - testSubject.Set(newItems, null); + testSubject.Set([], newConfigurationScope); - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(issueViz1); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(issueViz3); - } - - [TestMethod] - public void GetAnalysisInformation_NoInformation_ReturnsNull() - { - var testSubject = CreateTestSubject(); - testSubject.Set(Enumerable.Empty(), null); + var result = testSubject.ConfigurationScope; + result.Should().BeSameAs(newConfigurationScope); + } - var result = testSubject.GetAnalysisInformation(); - result.Should().BeNull(); - } - - [TestMethod] - public void GetAnalysisInformation_HasInformation_ReturnsInformation() - { - var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); - - var testSubject = CreateTestSubject(); - testSubject.Set(Enumerable.Empty(), analysisInformation); - - var result = testSubject.GetAnalysisInformation(); - result.Should().BeSameAs(analysisInformation); - } - - [TestMethod] - public void Remove_IssueKeyIsNull_ArgumentNullException() - { - var testSubject = CreateTestSubject(); + [TestMethod] + public void Remove_IssueKeyIsNull_ArgumentNullException() + { + Action act = () => testSubject.Remove(null); - Action act = () => testSubject.Remove(null); - - act.Should().Throw().And.ParamName.Should().Be("issueKey"); - } + act.Should().Throw().And.ParamName.Should().Be("issueKey"); + } - [TestMethod] - public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() - { - var testSubject = CreateTestSubject(); + [TestMethod] + public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() + { + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + testSubject.Remove("some unknown key"); - testSubject.Remove("some unknown key"); + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEmpty(); + } - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEmpty(); - } + [TestMethod] + public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() + { + var existingIssue = SetupIssueViz("key1"); - [TestMethod] - public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); + testSubject.Set(new[] { existingIssue }, "some config scope"); - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, null); + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + testSubject.Remove("some unknown key"); - testSubject.Remove("some unknown key"); + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue); + } - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } - - [TestMethod] - public void Remove_IssueFound_IssueIsRemovedAndEventIsRaised() - { - var existingIssue1 = SetupIssueViz("key1"); - var existingIssue2 = SetupIssueViz("key2"); - var existingIssue3 = SetupIssueViz("key3"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] {existingIssue1, existingIssue2, existingIssue3}, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + [TestMethod] + public void Remove_IssueFound_IssueIsRemovedAndEventIsRaised() + { + var existingIssue1 = SetupIssueViz("key1"); + var existingIssue2 = SetupIssueViz("key2"); + var existingIssue3 = SetupIssueViz("key3"); - testSubject.Remove("key2"); + testSubject.Set([existingIssue1, existingIssue2, existingIssue3], "some config scope"); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); - } + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - [TestMethod] - public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() - { - var existingIssue1 = SetupIssueViz("key1"); - var existingIssue2 = SetupIssueViz("key1"); - var existingIssue3 = SetupIssueViz("key1"); + testSubject.Remove("key2"); - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, null); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + var eventArgs = (IssuesChangedEventArgs)eventHandlerMock.ReceivedCalls().Single().GetArguments()[1]!; + eventArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); + eventArgs.AddedIssues.Should().BeEmpty(); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); + } - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + [TestMethod] + public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() + { + var existingIssue1 = SetupIssueViz("key1"); + var existingIssue2 = SetupIssueViz("key1"); + var existingIssue3 = SetupIssueViz("key1"); - testSubject.Remove("key1"); + testSubject.Set([existingIssue1, existingIssue2, existingIssue3], "some config scope"); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); - } + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - [TestMethod] - public void Add_IssueIsNull_ArgumentNullException() - { - var testSubject = CreateTestSubject(); + testSubject.Remove("key1"); - Action act = () => testSubject.Add(null); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + var eventArgs = (IssuesChangedEventArgs)eventHandlerMock.ReceivedCalls().Single().GetArguments()[1]!; + eventArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); + eventArgs.AddedIssues.Should().BeEmpty(); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); + } - act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); - } + [TestMethod] + public void Add_IssueIsNull_ArgumentNullException() + { + Action act = () => testSubject.Add(null); - [TestMethod] - public void Add_NoAnalysisInformation_IssueIgnoredAndNoEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); + act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); + } - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue}, null); + [TestMethod] + public void Add_NoConfigScope_IssueIgnoredAndNoEventIsRaised() + { + testSubject.Set([], null); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - testSubject.Add(SetupIssueViz()); + testSubject.Add(SetupIssueViz()); - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEmpty(); + } - [TestMethod] - public void Add_HasAnalysisInformation_IssueAddedAndEventIsRaised() - { - var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); - var existingIssue = SetupIssueViz("key1"); + [TestMethod] + public void Add_HasConfigScope_IssueAddedAndEventIsRaised() + { + var existingIssue = SetupIssueViz("key1"); - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, analysisInformation); + testSubject.Set([existingIssue], "some config scope"); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var newIssue = SetupIssueViz(); - testSubject.Add(newIssue); + var newIssue = SetupIssueViz(); + testSubject.Add(newIssue); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEmpty(); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newIssue); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue, newIssue); - } + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + var eventArgs = (IssuesChangedEventArgs)eventHandlerMock.ReceivedCalls().Single().GetArguments()[1]!; + eventArgs.RemovedIssues.Should().BeEmpty(); + eventArgs.AddedIssues.Should().BeEquivalentTo(newIssue); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue, newIssue); + } - [TestMethod] - public void Add_DuplicateIssue_IssueIgnoredAndNoEventIsRaised() - { - var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); - var issueKey = "key1"; - var existingIssue = SetupIssueViz(issueKey); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, analysisInformation); + [TestMethod] + public void Add_DuplicateIssue_IssueIgnoredAndNoEventIsRaised() + { + var issueKey = "key1"; + var existingIssue = SetupIssueViz(issueKey); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + testSubject.Set([existingIssue], "some config scope"); - var newIssue = SetupIssueViz(issueKey); - testSubject.Add(newIssue); + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } + var newIssue = SetupIssueViz(issueKey); + testSubject.Add(newIssue); - private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) - { - issueKey ??= Guid.NewGuid().ToString(); + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue); + } - var taintIssue = new Mock(); - taintIssue.Setup(x => x.IssueKey).Returns(issueKey); + private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) + { + issueKey ??= Guid.NewGuid().ToString(); - var issueViz = new Mock(); - issueViz.Setup(x => x.Issue).Returns(taintIssue.Object); + var taintIssue = Substitute.For(); + taintIssue.IssueKey.Returns(issueKey); - return issueViz.Object; - } + var issueViz = Substitute.For(); + issueViz.Issue.Returns(taintIssue); - private ITaintStore CreateTestSubject() - { - return new TaintStore(); - } + return issueViz; } } diff --git a/src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs b/src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs deleted file mode 100644 index 6cfc54395b..0000000000 --- a/src/IssueViz.Security/Taint/TaintIssuesBindingMonitor.cs +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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; -using System.ComponentModel.Composition; -using System.Threading.Tasks; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; - -namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint -{ - /// - /// Listens to binding changes and triggers fetching of taint vulnerabilities from the connected server. - /// Doesn't do initial sync - only triggers the fetch when the binding changes. - /// - internal interface ITaintIssuesBindingMonitor : IDisposable - { - } - - [Export(typeof(ITaintIssuesBindingMonitor))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal sealed class TaintIssuesBindingMonitor : ITaintIssuesBindingMonitor - { - private readonly IActiveSolutionBoundTracker activeSolutionBoundTracker; - private readonly IFolderWorkspaceMonitor folderWorkspaceMonitor; - private readonly ITaintIssuesSynchronizer taintIssuesSynchronizer; - - [ImportingConstructor] - public TaintIssuesBindingMonitor(IActiveSolutionBoundTracker activeSolutionBoundTracker, - IFolderWorkspaceMonitor folderWorkspaceMonitor, - ITaintIssuesSynchronizer taintIssuesSynchronizer) - { - this.activeSolutionBoundTracker = activeSolutionBoundTracker; - this.folderWorkspaceMonitor = folderWorkspaceMonitor; - this.taintIssuesSynchronizer = taintIssuesSynchronizer; - - folderWorkspaceMonitor.FolderWorkspaceInitialized += FolderWorkspaceInitializedEvent_FolderWorkspaceInitialized; - activeSolutionBoundTracker.SolutionBindingChanged += ActiveSolutionBoundTracker_SolutionBindingChanged; - activeSolutionBoundTracker.SolutionBindingUpdated += ActiveSolutionBoundTracker_SolutionBindingUpdated; - } - - private async void FolderWorkspaceInitializedEvent_FolderWorkspaceInitialized(object sender, EventArgs e) - { - await Sync(); - } - - private async void ActiveSolutionBoundTracker_SolutionBindingUpdated(object sender, EventArgs e) - { - await Sync(); - } - - private async void ActiveSolutionBoundTracker_SolutionBindingChanged(object sender, ActiveSolutionBindingEventArgs e) - { - await Sync(); - } - - private async Task Sync() - { - await taintIssuesSynchronizer.SynchronizeWithServer(); - } - - public void Dispose() - { - folderWorkspaceMonitor.FolderWorkspaceInitialized -= FolderWorkspaceInitializedEvent_FolderWorkspaceInitialized; - activeSolutionBoundTracker.SolutionBindingChanged -= ActiveSolutionBoundTracker_SolutionBindingChanged; - activeSolutionBoundTracker.SolutionBindingUpdated -= ActiveSolutionBoundTracker_SolutionBindingUpdated; - } - } -} diff --git a/src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs b/src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs new file mode 100644 index 0000000000..c1eb071c4e --- /dev/null +++ b/src/IssueViz.Security/Taint/TaintIssuesConfigurationScopeMonitor.cs @@ -0,0 +1,56 @@ +/* + * 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 Microsoft.VisualStudio.Threading; +using SonarLint.VisualStudio.SLCore.State; + +namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint +{ + /// + /// Listens to binding changes and triggers fetching of taint vulnerabilities from the connected server. + /// Doesn't do initial sync - only triggers the fetch when the binding changes. + /// + internal interface ITaintIssuesBindingMonitor : IDisposable; + + [Export(typeof(ITaintIssuesBindingMonitor))] + [PartCreationPolicy(CreationPolicy.Shared)] + internal sealed class TaintIssuesConfigurationScopeMonitor : ITaintIssuesBindingMonitor + { + private readonly IActiveConfigScopeTracker activeConfigScopeTracker; + private readonly ITaintIssuesSynchronizer taintIssuesSynchronizer; + + [ImportingConstructor] + public TaintIssuesConfigurationScopeMonitor(IActiveConfigScopeTracker activeConfigScopeTracker, + ITaintIssuesSynchronizer taintIssuesSynchronizer) + { + this.activeConfigScopeTracker = activeConfigScopeTracker; + this.taintIssuesSynchronizer = taintIssuesSynchronizer; + + this.activeConfigScopeTracker.CurrentConfigurationScopeChanged += ActiveConfigScopeTrackerOnCurrentConfigurationScopeChanged; + } + + private void ActiveConfigScopeTrackerOnCurrentConfigurationScopeChanged(object sender, EventArgs e) => + taintIssuesSynchronizer.UpdateTaintVulnerabilitiesAsync(activeConfigScopeTracker.Current).Forget(); + + public void Dispose() => + activeConfigScopeTracker.CurrentConfigurationScopeChanged -= ActiveConfigScopeTrackerOnCurrentConfigurationScopeChanged; + } +} diff --git a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs index 826aca1196..5dccbe2476 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -18,185 +18,173 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.Core.Binding; +using SonarLint.VisualStudio.Core.Synchronization; using SonarLint.VisualStudio.Infrastructure.VS; -using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.TaintList; -using SonarQube.Client; +using SonarLint.VisualStudio.SLCore.Core; +using SonarLint.VisualStudio.SLCore.Service.Taint; +using SonarLint.VisualStudio.SLCore.State; using VSShellInterop = Microsoft.VisualStudio.Shell.Interop; -namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint +namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint; + +internal interface ITaintIssuesSynchronizer { - internal interface ITaintIssuesSynchronizer - { - /// - /// Fetches taint vulnerabilities from the server, converts them into visualizations and populates . - /// - Task SynchronizeWithServer(); - } + /// + /// Fetches taint vulnerabilities from the server, converts them into visualizations and populates . + /// + Task UpdateTaintVulnerabilitiesAsync(ConfigurationScope configurationScope); +} - [Export(typeof(ITaintIssuesSynchronizer))] - [PartCreationPolicy(CreationPolicy.Shared)] - internal sealed class TaintIssuesSynchronizer : ITaintIssuesSynchronizer +[Export(typeof(ITaintIssuesSynchronizer))] +[PartCreationPolicy(CreationPolicy.Shared)] +internal sealed class TaintIssuesSynchronizer : ITaintIssuesSynchronizer +{ + private readonly IAsyncLock asyncLock; + private readonly ITaintIssueToIssueVisualizationConverter converter; + private readonly ILogger logger; + private readonly ISLCoreServiceProvider slCoreServiceProvider; + private readonly ITaintStore taintStore; + private readonly IThreadHandling threadHandling; + private readonly IToolWindowService toolWindowService; + private readonly IVsUIServiceOperation vSServiceOperation; + + [ImportingConstructor] + public TaintIssuesSynchronizer( + ITaintStore taintStore, + ISLCoreServiceProvider slCoreServiceProvider, + ITaintIssueToIssueVisualizationConverter converter, + IToolWindowService toolWindowService, + IVsUIServiceOperation vSServiceOperation, + IThreadHandling threadHandling, + IAsyncLockFactory asyncLockFactory, + ILogger logger) { - private static readonly Version MinimumRequiredSonarQubeVersion = new Version(8, 6); - - private readonly ITaintStore taintStore; - private readonly ISonarQubeService sonarQubeService; - private readonly ITaintIssueToIssueVisualizationConverter converter; - private readonly IConfigurationProvider configurationProvider; - private readonly IToolWindowService toolWindowService; - private readonly IStatefulServerBranchProvider serverBranchProvider; - private readonly IVsUIServiceOperation vSServiceOperation; - private readonly ILogger logger; - - [ImportingConstructor] - public TaintIssuesSynchronizer(ITaintStore taintStore, - ISonarQubeService sonarQubeService, - ITaintIssueToIssueVisualizationConverter converter, - IConfigurationProvider configurationProvider, - IToolWindowService toolWindowService, - IStatefulServerBranchProvider serverBranchProvider, - IVsUIServiceOperation vSServiceOperation, - ILogger logger) - { - this.taintStore = taintStore; - this.sonarQubeService = sonarQubeService; - this.converter = converter; - this.configurationProvider = configurationProvider; - this.toolWindowService = toolWindowService; - this.serverBranchProvider = serverBranchProvider; - this.vSServiceOperation = vSServiceOperation; - this.logger = logger; - } - - public async Task SynchronizeWithServer() - { - return; // todo https://sonarsource.atlassian.net/browse/SLVS-1592 - - // try - // { - // var bindingConfiguration = configurationProvider.GetConfiguration(); - // - // if (IsStandalone(bindingConfiguration) || !IsConnected(out var serverInfo) || !IsFeatureSupported(serverInfo)) - // { - // HandleNoTaintIssues(); - // return; - // } - // - // var projectKey = bindingConfiguration.Project.ServerProjectKey; - // var serverBranch = await serverBranchProvider.GetServerBranchNameAsync(CancellationToken.None); - // - // var taintVulnerabilities = await sonarQubeService.GetTaintVulnerabilitiesAsync(projectKey, - // serverBranch, - // CancellationToken.None); - // - // logger.WriteLine(TaintResources.Synchronizer_NumberOfServerIssues, taintVulnerabilities.Count); - // - // var analysisInformation = await GetAnalysisInformation(projectKey, serverBranch); - // var taintIssueVizs = taintVulnerabilities.Select(converter.Convert).ToArray(); - // taintStore.Set(taintIssueVizs, analysisInformation); - // - // var hasTaintIssues = taintVulnerabilities.Count > 0; - // - // if (!hasTaintIssues) - // { - // UpdateTaintIssuesUIContext(false); - // } - // else - // { - // UpdateTaintIssuesUIContext(true); - // - // // We need the tool window content to exist so the issues are filtered and the - // // tool window caption is updated. See the "EnsureToolWindowExists" method comment - // // for more information. - // toolWindowService.EnsureToolWindowExists(TaintToolWindow.ToolWindowId); - // } - // } - // catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) - // { - // logger.WriteLine(TaintResources.Synchronizer_Failure, ex); - // HandleNoTaintIssues(); - // } - } + this.taintStore = taintStore; + this.slCoreServiceProvider = slCoreServiceProvider; + this.converter = converter; + this.toolWindowService = toolWindowService; + this.vSServiceOperation = vSServiceOperation; + asyncLock = asyncLockFactory.Create(); + this.threadHandling = threadHandling; + this.logger = logger; + } - private bool IsStandalone(BindingConfiguration bindingConfiguration) + public Task UpdateTaintVulnerabilitiesAsync(ConfigurationScope configurationScope) => + threadHandling.RunOnBackgroundThread(async () => { - if (bindingConfiguration.Mode == SonarLintMode.Standalone) + using (await asyncLock.AcquireAsync()) { - logger.WriteLine(TaintResources.Synchronizer_NotInConnectedMode); - return true; + await PerformSynchronizationInternalAsync(configurationScope); } + }); - return false; - } - - private bool IsConnected(out ServerInfo serverInfo) + private async Task PerformSynchronizationInternalAsync(ConfigurationScope configurationScope) + { + try { - serverInfo = sonarQubeService.GetServerInfo(); - - if (serverInfo != null) + if (!IsConnectedModeConfigScope(configurationScope) + || !IsConfigScopeReady(configurationScope) + || !TryGetSLCoreService(out var taintService)) { - return true; + HandleNoTaintIssues(); + return; } - logger.WriteLine(TaintResources.Synchronizer_ServerNotConnected); - return false; - } - - private bool IsFeatureSupported(ServerInfo serverInfo) - { - if (serverInfo.ServerType == ServerType.SonarCloud || - serverInfo.Version >= MinimumRequiredSonarQubeVersion) + if (IsAlreadyInitializedForConfigScope(configurationScope)) { - return true; + return; } - logger.WriteLine(TaintResources.Synchronizer_UnsupportedSQVersion, serverInfo.Version, DocumentationLinks.TaintVulnerabilities); - return false; + var taintsResponse = await taintService.ListAllAsync(new ListAllTaintsParams(configurationScope.Id, true)); + logger.WriteLine(TaintResources.Synchronizer_NumberOfServerIssues, taintsResponse.taintVulnerabilities.Count); + + taintStore.Set(taintsResponse.taintVulnerabilities.Select(x => converter.Convert(x, configurationScope.RootPath)).ToArray(), configurationScope.Id); + + HandleUIContextUpdate(taintsResponse.taintVulnerabilities.Count); + } + catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) + { + logger.WriteLine(TaintResources.Synchronizer_Failure, ex); + HandleNoTaintIssues(); } + } - private async Task GetAnalysisInformation(string projectKey, string branchName) + private bool TryGetSLCoreService(out ITaintVulnerabilityTrackingSlCoreService taintService) + { + var result = slCoreServiceProvider.TryGetTransientService(out taintService); + if (!result) { - Debug.Assert(branchName != null, "BranchName should not be null when in Connected Mode"); + logger.WriteLine(TaintResources.Synchronizer_SLCoreNotReady); + } + return result; + } - var branches = await sonarQubeService.GetProjectBranchesAsync(projectKey, CancellationToken.None); + private bool IsConfigScopeReady(ConfigurationScope configurationScope) + { + var isReady = configurationScope.RootPath is not null; + if (!isReady) + { + logger.LogVerbose(TaintResources.Synchronizer_Verbose_ConfigScopeNotReady); + } + return isReady; + } - var issuesBranch = branches.FirstOrDefault(x => x.Name.Equals(branchName)); + private bool IsAlreadyInitializedForConfigScope(ConfigurationScope configurationScope) + { + var isAlreadyInitialized = taintStore.ConfigurationScope == configurationScope.Id; + if (!isAlreadyInitialized) + { + logger.LogVerbose(TaintResources.Synchronizer_Verbose_AlreadyInitialized); + } + return isAlreadyInitialized; + } - Debug.Assert(issuesBranch != null, "Should always find a matching branch"); + private void HandleUIContextUpdate(int taintsCount) + { + if (taintsCount > 0) + { + UpdateTaintIssuesUIContext(true); - return new AnalysisInformation(issuesBranch.Name, issuesBranch.LastAnalysisTimestamp); + // We need the tool window content to exist so the issues are filtered and the + // tool window caption is updated. See the "EnsureToolWindowExists" method comment + // for more information. + toolWindowService.EnsureToolWindowExists(TaintToolWindow.ToolWindowId); } - - private void HandleNoTaintIssues() + else { - ClearStore(); UpdateTaintIssuesUIContext(false); } + } - private void ClearStore() + private bool IsConnectedModeConfigScope(ConfigurationScope configurationScope) + { + if (configurationScope is { SonarProjectId: not null }) { - taintStore.Set(Enumerable.Empty(), null); + return true; } - private void UpdateTaintIssuesUIContext(bool hasTaintIssues) - { - vSServiceOperation.Execute( - monitorSelection => - { - Guid localGuid = TaintIssuesExistUIContext.Guid; - - monitorSelection.GetCmdUIContextCookie(ref localGuid, out var cookie); - monitorSelection.SetCmdUIContext(cookie, hasTaintIssues ? 1 : 0); - }); - } + logger.WriteLine(TaintResources.Synchronizer_NotInConnectedMode); + return false; + } + + private void HandleNoTaintIssues() + { + ClearStore(); + UpdateTaintIssuesUIContext(false); } + + private void ClearStore() => taintStore.Set([], null); + + private void UpdateTaintIssuesUIContext(bool hasTaintIssues) => + vSServiceOperation.Execute( + monitorSelection => + { + var localGuid = TaintIssuesExistUIContext.Guid; + + monitorSelection.GetCmdUIContextCookie(ref localGuid, out var cookie); + monitorSelection.SetCmdUIContext(cookie, hasTaintIssues ? 1 : 0); + }); } diff --git a/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs b/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs index ecdf4726cb..e71ec7debf 100644 --- a/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs +++ b/src/IssueViz.Security/Taint/TaintList/ViewModels/TaintIssuesControlViewModel.cs @@ -164,7 +164,7 @@ INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand ISonarQubeService sonarQubeService, INavigateToRuleDescriptionCommand navigateToRuleDescriptionCommand, IThreadHandling threadHandling) - { + { this.threadHandling = threadHandling; unfilteredIssues = new ObservableCollection(); AllowMultiThreadedAccessToIssuesCollection(); @@ -301,7 +301,7 @@ private void UpdateIssues() taintIssueViewModel.TaintIssueViz.PropertyChanged += OnTaintIssuePropertyChanged; } - AnalysisInformation = store.GetAnalysisInformation(); + AnalysisInformation = new AnalysisInformation("stub", DateTimeOffset.Now); NotifyPropertyChanged(nameof(HasServerIssues)); NotifyPropertyChanged(nameof(AnalysisInformation)); diff --git a/src/IssueViz.Security/Taint/TaintResources.Designer.cs b/src/IssueViz.Security/Taint/TaintResources.Designer.cs index e3ca741cce..b6e6be43a2 100644 --- a/src/IssueViz.Security/Taint/TaintResources.Designer.cs +++ b/src/IssueViz.Security/Taint/TaintResources.Designer.cs @@ -9,8 +9,8 @@ namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint { using System; - - + + /// /// A strongly-typed resource class, for looking up localized strings, etc. /// @@ -22,15 +22,15 @@ namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint { [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class TaintResources { - + private static global::System.Resources.ResourceManager resourceMan; - + private static global::System.Globalization.CultureInfo resourceCulture; - + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal TaintResources() { } - + /// /// Returns the cached ResourceManager instance used by this class. /// @@ -44,7 +44,7 @@ internal TaintResources() { return resourceMan; } } - + /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. @@ -58,7 +58,7 @@ internal TaintResources() { resourceCulture = value; } } - + /// /// Looks up a localized string similar to [Taint] Failed to synchronize taint vulnerabilities with the connected server: {0}.. /// @@ -67,7 +67,7 @@ internal static string Synchronizer_Failure { return ResourceManager.GetString("Synchronizer_Failure", resourceCulture); } } - + /// /// Looks up a localized string similar to [Taint] Unable to fetch taint vulnerabilities: not in connected mode.. /// @@ -76,7 +76,7 @@ internal static string Synchronizer_NotInConnectedMode { return ResourceManager.GetString("Synchronizer_NotInConnectedMode", resourceCulture); } } - + /// /// Looks up a localized string similar to [Taint] Fetched {0} taint vulnerabilities.. /// @@ -85,26 +85,34 @@ internal static string Synchronizer_NumberOfServerIssues { return ResourceManager.GetString("Synchronizer_NumberOfServerIssues", resourceCulture); } } - + /// - /// Looks up a localized string similar to [Taint] Unable to fetch taint vulnerabilities: a connection to the server is not yet established.. + /// Looks up a localized string similar to [Taint] Unable to fetch taint vulnerabilities: SLCore backend is not available.. /// - internal static string Synchronizer_ServerNotConnected { + internal static string Synchronizer_SLCoreNotReady { get { - return ResourceManager.GetString("Synchronizer_ServerNotConnected", resourceCulture); + return ResourceManager.GetString("Synchronizer_SLCoreNotReady", resourceCulture); } } - + + /// + /// Looks up a localized string similar to [Taint Sync] Taint storage has already been initialized for current configuration scope. + /// + internal static string Synchronizer_Verbose_AlreadyInitialized { + get { + return ResourceManager.GetString("Synchronizer_Verbose_AlreadyInitialized", resourceCulture); + } + } + /// - /// Looks up a localized string similar to [Taint] Displaying taint vulnerabilities in the IDE requires SonarQube Server v8.6 or later, or SonarQube Cloud. Connected SonarQube Server version: v{0} - /// Visit {1} to find out more about this and other SonarQube for Visual Studio features.. + /// Looks up a localized string similar to [Taint Sync] Configuration scope root hasn't been initialized.... /// - internal static string Synchronizer_UnsupportedSQVersion { + internal static string Synchronizer_Verbose_ConfigScopeNotReady { get { - return ResourceManager.GetString("Synchronizer_UnsupportedSQVersion", resourceCulture); + return ResourceManager.GetString("Synchronizer_Verbose_ConfigScopeNotReady", resourceCulture); } } - + /// /// Looks up a localized string similar to [Taint] Finished initializing taint issues synchronization package.. /// @@ -113,7 +121,7 @@ internal static string SyncPackage_Initialized { return ResourceManager.GetString("SyncPackage_Initialized", resourceCulture); } } - + /// /// Looks up a localized string similar to [Taint] Initializing taint issues synchronization package.... /// @@ -122,7 +130,7 @@ internal static string SyncPackage_Initializing { return ResourceManager.GetString("SyncPackage_Initializing", resourceCulture); } } - + /// /// Looks up a localized string similar to Taint issue with id: {0} has no defined severity. /// diff --git a/src/IssueViz.Security/Taint/TaintResources.resx b/src/IssueViz.Security/Taint/TaintResources.resx index 5415ad98ee..1915a37e92 100644 --- a/src/IssueViz.Security/Taint/TaintResources.resx +++ b/src/IssueViz.Security/Taint/TaintResources.resx @@ -123,8 +123,8 @@ [Taint] Unable to fetch taint vulnerabilities: not in connected mode. - - [Taint] Unable to fetch taint vulnerabilities: a connection to the server is not yet established. + + [Taint] Unable to fetch taint vulnerabilities: SLCore backend is not available. [Taint] Fetched {0} taint vulnerabilities. @@ -142,4 +142,10 @@ Taint issue with id: {0} has no defined severity + + [Taint Sync] Configuration scope root hasn't been initialized... + + + [Taint Sync] Taint storage has already been initialized for current configuration scope + \ No newline at end of file diff --git a/src/IssueViz.Security/Taint/TaintStore.cs b/src/IssueViz.Security/Taint/TaintStore.cs index 7120f10c49..35db00dd10 100644 --- a/src/IssueViz.Security/Taint/TaintStore.cs +++ b/src/IssueViz.Security/Taint/TaintStore.cs @@ -18,10 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Linq; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; @@ -34,12 +31,9 @@ internal interface ITaintStore : IIssuesStore /// Removes all existing visualizations and initializes the store to the given collection. /// Can be called multiple times. /// - void Set(IEnumerable issueVisualizations, AnalysisInformation analysisInformation); + void Set(IReadOnlyCollection issueVisualizations, string newConfigurationScope); - /// - /// Returns additional analysis information for the existing visualizations in the store. - /// - AnalysisInformation GetAnalysisInformation(); + string ConfigurationScope { get; } /// /// Add the given issue to the existing list of visualizations. @@ -61,21 +55,19 @@ internal sealed class TaintStore : ITaintStore { public event EventHandler IssuesChanged; - private static readonly object Locker = new object(); + private readonly object locker = new object(); + private string configurationScope; private List taintVulnerabilities = new List(); - private AnalysisInformation analysisInformation; public IReadOnlyCollection GetAll() { - lock (Locker) + lock (locker) { return taintVulnerabilities.ToList(); } } - public AnalysisInformation GetAnalysisInformation() => analysisInformation; - public void Add(IAnalysisIssueVisualization issueVisualization) { if (issueVisualization == null) @@ -83,9 +75,9 @@ public void Add(IAnalysisIssueVisualization issueVisualization) throw new ArgumentNullException(nameof(issueVisualization)); } - lock (Locker) + lock (locker) { - if (analysisInformation == null) + if (configurationScope == null) { return; } @@ -97,8 +89,9 @@ public void Add(IAnalysisIssueVisualization issueVisualization) taintVulnerabilities.Add(issueVisualization); - NotifyIssuesChanged(Array.Empty(), new[] { issueVisualization }); } + + NotifyIssuesChanged([], [issueVisualization]); } public void Remove(string issueKey) @@ -108,8 +101,15 @@ public void Remove(string issueKey) throw new ArgumentNullException(nameof(issueKey)); } - lock (Locker) + IAnalysisIssueVisualization valueToRemove; + + lock (locker) { + if (configurationScope == null) + { + return; + } + var indexToRemove = taintVulnerabilities.FindIndex(issueViz => ((ITaintIssue)issueViz.Issue).IssueKey.Equals(issueKey)); @@ -118,31 +118,51 @@ public void Remove(string issueKey) return; } - var valueToRemove = taintVulnerabilities[indexToRemove]; + valueToRemove = taintVulnerabilities[indexToRemove]; taintVulnerabilities.RemoveAt(indexToRemove); - NotifyIssuesChanged(new[] { valueToRemove }, Array.Empty()); } + + NotifyIssuesChanged([valueToRemove], []); } - public void Set(IEnumerable issueVisualizations, AnalysisInformation analysisInformation) + public void Set(IReadOnlyCollection issueVisualizations, string newConfigurationScope) { if (issueVisualizations == null) { throw new ArgumentNullException(nameof(issueVisualizations)); } - lock (Locker) + if (issueVisualizations.Count > 0 && newConfigurationScope == null) { - this.analysisInformation = analysisInformation; + throw new ArgumentNullException(nameof(newConfigurationScope)); + } + IAnalysisIssueVisualization[] removedIssues; + IAnalysisIssueVisualization[] addedIssues; + + lock (locker) + { var oldIssues = taintVulnerabilities; taintVulnerabilities = issueVisualizations.ToList(); + configurationScope = newConfigurationScope; + + + removedIssues = oldIssues.Except(taintVulnerabilities, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); + addedIssues = taintVulnerabilities.Except(oldIssues, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); + } - var removedIssues = oldIssues.Except(taintVulnerabilities, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); - var addedIssues = taintVulnerabilities.Except(oldIssues, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); + NotifyIssuesChanged(removedIssues, addedIssues); + } - NotifyIssuesChanged(removedIssues, addedIssues); + public string ConfigurationScope + { + get + { + lock (locker) + { + return configurationScope; + } } } diff --git a/src/IssueViz.Security/Taint/TaintSyncPackage.cs b/src/IssueViz.Security/Taint/TaintSyncPackage.cs index 7c940e73b5..6801b8b57b 100644 --- a/src/IssueViz.Security/Taint/TaintSyncPackage.cs +++ b/src/IssueViz.Security/Taint/TaintSyncPackage.cs @@ -25,6 +25,7 @@ using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Infrastructure.VS; +using SonarLint.VisualStudio.SLCore.State; using Task = System.Threading.Tasks.Task; namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint @@ -63,7 +64,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke await ThreadHandling.Instance.SwitchToBackgroundThread(); - await taintIssuesSynchronizer.SynchronizeWithServer(); + await taintIssuesSynchronizer.UpdateTaintVulnerabilitiesAsync(componentModel.GetService().Current); logger.WriteLine(TaintResources.SyncPackage_Initialized); }