From 7bfec6b48750e06861501044498abc6700fddd1f Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Fri, 8 Nov 2024 10:38:02 +0100 Subject: [PATCH 1/9] SLVS-1592 Update TaintStore with taints from SLCore --- src/Core/IFolderWorkspaceMonitor.cs | 2 +- .../Taint/TaintIssuesBindingMonitorTests.cs | 107 --- ...intIssuesConfigurationScopeMonitorTests.cs | 63 ++ .../TaintIssuesControlViewModelTests.cs | 49 +- .../Taint/TaintStoreTests.cs | 782 +++++++++--------- .../Taint/TaintIssuesBindingMonitor.cs | 86 -- .../TaintIssuesConfigurationScopeMonitor.cs | 56 ++ .../Taint/TaintIssuesSynchronizer.cs | 253 +++--- .../ViewModels/TaintIssuesControlViewModel.cs | 4 +- src/IssueViz.Security/Taint/TaintStore.cs | 47 +- .../Taint/TaintSyncPackage.cs | 3 +- 11 files changed, 651 insertions(+), 801 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/IFolderWorkspaceMonitor.cs b/src/Core/IFolderWorkspaceMonitor.cs index ce5a2f8a94..080f08d883 100644 --- a/src/Core/IFolderWorkspaceMonitor.cs +++ b/src/Core/IFolderWorkspaceMonitor.cs @@ -34,6 +34,6 @@ public interface IFolderWorkspaceMonitor /// We need this event since certain parts of our code, i.e. , /// rely on VsHierarchy being initialized by the time they're called. /// - event EventHandler FolderWorkspaceInitialized; + event EventHandler FolderWorkspaceInitialized; // todo } } 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..32d6c8c56f --- /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 +{ + [TestMethod] + public void Ctor_SubscribesToConfigurationScopeEvents() + { + var activeConfigScopeTracker = Substitute.For(); + + _ = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, Substitute.For()); + + activeConfigScopeTracker.Received().CurrentConfigurationScopeChanged += Arg.Any(); + } + + [TestMethod] + public void Dispose_UnsubscribesToConfigurationScopeEvents() + { + var activeConfigScopeTracker = Substitute.For(); + var testSubject = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, Substitute.For()); + + testSubject.Dispose(); + + activeConfigScopeTracker.Received().CurrentConfigurationScopeChanged -= Arg.Any(); + } + + [TestMethod] + public void ConfigScopeChangedEvent_CallsTaintSynchronizer() + { + var activeConfigScopeTracker = Substitute.For(); + var configurationScope = new ConfigurationScope("config scope"); + activeConfigScopeTracker.Current.Returns(configurationScope); + var taintIssuesSynchronizer = Substitute.For(); + _ = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, taintIssuesSynchronizer); + + activeConfigScopeTracker.CurrentConfigurationScopeChanged += Raise.Event(); + + taintIssuesSynchronizer.Received(1).UpdateTaintVulnerabilitiesAsync(configurationScope); + } +} 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..585b5953d2 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs @@ -1,391 +1,391 @@ -/* - * 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.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 -{ - [TestClass] - public class TaintStoreTests - { - [TestMethod] - public void MefCtor_CheckExports() - { - var batch = new CompositionBatch(); - - 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); - - storeImport.Import.Should().NotBeNull(); - issuesStoreImport.Import.Should().NotBeNull(); - - 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)); - - var issuesList1 = testSubject.GetAll(); - testSubject.Add(SetupIssueViz()); - var issuesList2 = testSubject.GetAll(); - - issuesList1.Count.Should().Be(2); - issuesList2.Count.Should().Be(3); - } - - [TestMethod] - public void Set_NullCollection_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Set(null, null); - - act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); - } - - [TestMethod] - public void Set_NoSubscribersToIssuesChangedEvent_NoException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Set(new[] { SetupIssueViz() }, null); - - act.Should().NotThrow(); - } - - [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); - } - - [TestMethod] - public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Set(Enumerable.Empty(), null); - - testSubject.GetAll().Should().BeEmpty(); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEmpty(); - } - - [TestMethod] - public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, null); - - 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().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); - } - - [TestMethod] - public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var issueViz1 = SetupIssueViz("key1"); - var issueViz2 = SetupIssueViz("key2"); - var issueViz2NewObject = SetupIssueViz("key2"); - var issueViz3 = SetupIssueViz("key3"); - - var oldItems = new[] { issueViz1, issueViz2 }; - testSubject.Set(oldItems, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; - - var newItems = new[] { issueViz2NewObject, issueViz3}; - testSubject.Set(newItems, null); - - 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.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(); - - Action act = () => testSubject.Remove(null); - - act.Should().Throw().And.ParamName.Should().Be("issueKey"); - } - - [TestMethod] - public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("some unknown key"); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEmpty(); - } - - [TestMethod] - public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("some unknown key"); - - 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; }; - - testSubject.Remove("key2"); - - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); - } - - [TestMethod] - public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() - { - var existingIssue1 = SetupIssueViz("key1"); - var existingIssue2 = SetupIssueViz("key1"); - var existingIssue3 = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("key1"); - - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); - } - - [TestMethod] - public void Add_IssueIsNull_ArgumentNullException() - { - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Add(null); - - act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); - } - - [TestMethod] - public void Add_NoAnalysisInformation_IssueIgnoredAndNoEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue}, null); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Add(SetupIssueViz()); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } - - [TestMethod] - public void Add_HasAnalysisInformation_IssueAddedAndEventIsRaised() - { - var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); - var existingIssue = SetupIssueViz("key1"); - - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, analysisInformation); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - 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); - } - - [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); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - var newIssue = SetupIssueViz(issueKey); - testSubject.Add(newIssue); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } - - private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) - { - issueKey ??= Guid.NewGuid().ToString(); - - var taintIssue = new Mock(); - taintIssue.Setup(x => x.IssueKey).Returns(issueKey); - - var issueViz = new Mock(); - issueViz.Setup(x => x.Issue).Returns(taintIssue.Object); - - return issueViz.Object; - } - - private ITaintStore CreateTestSubject() - { - return new TaintStore(); - } - } -} +// /* +// * 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.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 +// { +// [TestClass] +// public class TaintStoreTests +// { +// [TestMethod] +// public void MefCtor_CheckExports() +// { +// var batch = new CompositionBatch(); +// +// 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); +// +// storeImport.Import.Should().NotBeNull(); +// issuesStoreImport.Import.Should().NotBeNull(); +// +// 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)); +// +// var issuesList1 = testSubject.GetAll(); +// testSubject.Add(SetupIssueViz()); +// var issuesList2 = testSubject.GetAll(); +// +// issuesList1.Count.Should().Be(2); +// issuesList2.Count.Should().Be(3); +// } +// +// [TestMethod] +// public void Set_NullCollection_ArgumentNullException() +// { +// var testSubject = CreateTestSubject(); +// +// Action act = () => testSubject.Set(null, null); +// +// act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); +// } +// +// [TestMethod] +// public void Set_NoSubscribersToIssuesChangedEvent_NoException() +// { +// var testSubject = CreateTestSubject(); +// +// Action act = () => testSubject.Set(new[] { SetupIssueViz() }, null); +// +// act.Should().NotThrow(); +// } +// +// [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); +// } +// +// [TestMethod] +// public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; +// testSubject.Set(oldItems, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Set(Enumerable.Empty(), null); +// +// testSubject.GetAll().Should().BeEmpty(); +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); +// suppliedArgs.AddedIssues.Should().BeEmpty(); +// } +// +// [TestMethod] +// public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; +// testSubject.Set(oldItems, null); +// +// 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().BeEquivalentTo(oldItems); +// suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); +// } +// +// [TestMethod] +// public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var issueViz1 = SetupIssueViz("key1"); +// var issueViz2 = SetupIssueViz("key2"); +// var issueViz2NewObject = SetupIssueViz("key2"); +// var issueViz3 = SetupIssueViz("key3"); +// +// var oldItems = new[] { issueViz1, issueViz2 }; +// testSubject.Set(oldItems, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; +// +// var newItems = new[] { issueViz2NewObject, issueViz3}; +// testSubject.Set(newItems, null); +// +// 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.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(); +// +// Action act = () => testSubject.Remove(null); +// +// act.Should().Throw().And.ParamName.Should().Be("issueKey"); +// } +// +// [TestMethod] +// public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() +// { +// var testSubject = CreateTestSubject(); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Remove("some unknown key"); +// +// callCount.Should().Be(0); +// testSubject.GetAll().Should().BeEmpty(); +// } +// +// [TestMethod] +// public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() +// { +// var existingIssue = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue }, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Remove("some unknown key"); +// +// 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; }; +// +// testSubject.Remove("key2"); +// +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); +// suppliedArgs.AddedIssues.Should().BeEmpty(); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); +// } +// +// [TestMethod] +// public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() +// { +// var existingIssue1 = SetupIssueViz("key1"); +// var existingIssue2 = SetupIssueViz("key1"); +// var existingIssue3 = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Remove("key1"); +// +// callCount.Should().Be(1); +// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); +// suppliedArgs.AddedIssues.Should().BeEmpty(); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); +// } +// +// [TestMethod] +// public void Add_IssueIsNull_ArgumentNullException() +// { +// var testSubject = CreateTestSubject(); +// +// Action act = () => testSubject.Add(null); +// +// act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); +// } +// +// [TestMethod] +// public void Add_NoAnalysisInformation_IssueIgnoredAndNoEventIsRaised() +// { +// var existingIssue = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue}, null); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// testSubject.Add(SetupIssueViz()); +// +// callCount.Should().Be(0); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue); +// } +// +// [TestMethod] +// public void Add_HasAnalysisInformation_IssueAddedAndEventIsRaised() +// { +// var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); +// var existingIssue = SetupIssueViz("key1"); +// +// var testSubject = CreateTestSubject(); +// testSubject.Set(new[] { existingIssue }, analysisInformation); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// 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); +// } +// +// [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); +// +// var callCount = 0; +// IssuesChangedEventArgs suppliedArgs = null; +// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; +// +// var newIssue = SetupIssueViz(issueKey); +// testSubject.Add(newIssue); +// +// callCount.Should().Be(0); +// testSubject.GetAll().Should().BeEquivalentTo(existingIssue); +// } +// +// private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) +// { +// issueKey ??= Guid.NewGuid().ToString(); +// +// var taintIssue = new Mock(); +// taintIssue.Setup(x => x.IssueKey).Returns(issueKey); +// +// var issueViz = new Mock(); +// issueViz.Setup(x => x.Issue).Returns(taintIssue.Object); +// +// return issueViz.Object; +// } +// +// private ITaintStore CreateTestSubject() +// { +// return new TaintStore(); +// } +// } +// } 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..31ffecff04 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -18,185 +18,148 @@ * 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, + IStatefulServerBranchProvider serverBranchProvider, + 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 (IsStandalone(configurationScope) || !slCoreServiceProvider.TryGetTransientService(out ITaintVulnerabilityTrackingSlCoreService 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 (!IsConfigScopeReady(configurationScope) || 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); - private async Task GetAnalysisInformation(string projectKey, string branchName) - { - Debug.Assert(branchName != null, "BranchName should not be null when in Connected Mode"); + taintStore.Set(taintsResponse.taintVulnerabilities.Select(x => converter.Convert(x, configurationScope.RootPath)), configurationScope.Id); - var branches = await sonarQubeService.GetProjectBranchesAsync(projectKey, CancellationToken.None); + HandleUIContextUpdate(taintsResponse); + } + catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) + { + logger.WriteLine(TaintResources.Synchronizer_Failure, ex); + HandleNoTaintIssues(); + } + } - var issuesBranch = branches.FirstOrDefault(x => x.Name.Equals(branchName)); + private static bool IsConfigScopeReady(ConfigurationScope configurationScope) => configurationScope.RootPath is not null; - Debug.Assert(issuesBranch != null, "Should always find a matching branch"); + private bool IsAlreadyInitializedForConfigScope(ConfigurationScope configurationScope) => taintStore.ConfigurationScope == configurationScope.Id; - return new AnalysisInformation(issuesBranch.Name, issuesBranch.LastAnalysisTimestamp); - } + private void HandleUIContextUpdate(ListAllTaintsResponse taintsResponse) + { + var hasTaintIssues = taintsResponse.taintVulnerabilities.Count > 0; - private void HandleNoTaintIssues() + if (!hasTaintIssues) { - ClearStore(); UpdateTaintIssuesUIContext(false); } - - private void ClearStore() + else { - taintStore.Set(Enumerable.Empty(), null); + 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); } + } - private void UpdateTaintIssuesUIContext(bool hasTaintIssues) + private bool IsStandalone(ConfigurationScope configurationScope) + { + if (configurationScope is { SonarProjectId: not null }) { - vSServiceOperation.Execute( - monitorSelection => - { - Guid localGuid = TaintIssuesExistUIContext.Guid; - - monitorSelection.GetCmdUIContextCookie(ref localGuid, out var cookie); - monitorSelection.SetCmdUIContext(cookie, hasTaintIssues ? 1 : 0); - }); + return false; } + + logger.WriteLine(TaintResources.Synchronizer_NotInConnectedMode); + return true; + } + + 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/TaintStore.cs b/src/IssueViz.Security/Taint/TaintStore.cs index 7120f10c49..77b2f5efb0 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(IEnumerable 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; } @@ -108,8 +100,13 @@ public void Remove(string issueKey) throw new ArgumentNullException(nameof(issueKey)); } - lock (Locker) + lock (locker) { + if (configurationScope == null) + { + return; + } + var indexToRemove = taintVulnerabilities.FindIndex(issueViz => ((ITaintIssue)issueViz.Issue).IssueKey.Equals(issueKey)); @@ -125,17 +122,16 @@ public void Remove(string issueKey) } } - public void Set(IEnumerable issueVisualizations, AnalysisInformation analysisInformation) + public void Set(IEnumerable issueVisualizations, string newConfigurationScope) { if (issueVisualizations == null) { throw new ArgumentNullException(nameof(issueVisualizations)); } - lock (Locker) + lock (locker) { - this.analysisInformation = analysisInformation; - + configurationScope = newConfigurationScope; var oldIssues = taintVulnerabilities; taintVulnerabilities = issueVisualizations.ToList(); @@ -146,6 +142,17 @@ public void Set(IEnumerable issueVisualizations, An } } + public string ConfigurationScope + { + get + { + lock (locker) + { + return configurationScope; + } + } + } + private void NotifyIssuesChanged( IReadOnlyCollection removedIssues, IReadOnlyCollection addedIssues) diff --git a/src/IssueViz.Security/Taint/TaintSyncPackage.cs b/src/IssueViz.Security/Taint/TaintSyncPackage.cs index c58e673612..496d75499d 100644 --- a/src/IssueViz.Security/Taint/TaintSyncPackage.cs +++ b/src/IssueViz.Security/Taint/TaintSyncPackage.cs @@ -29,6 +29,7 @@ using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.ServerSentEvents; +using SonarLint.VisualStudio.SLCore.State; using Task = System.Threading.Tasks.Task; namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint @@ -69,7 +70,7 @@ protected override async Task InitializeAsync(CancellationToken cancellationToke await ThreadHandling.Instance.SwitchToBackgroundThread(); - await taintIssuesSynchronizer.SynchronizeWithServer(); + await taintIssuesSynchronizer.UpdateTaintVulnerabilitiesAsync(componentModel.GetService().Current); taintServerEventsListener.ListenAsync().Forget(); logger.WriteLine(TaintResources.SyncPackage_Initialized); From 0bce40b54cf403ed563454403106809ac6f50a8b Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Mon, 11 Nov 2024 17:00:43 +0100 Subject: [PATCH 2/9] WIP tests --- src/Core/ThreadHandlingExtensions.cs | 6 +- .../Taint/TaintIssuesSynchronizerTests.cs | 1071 ++++++++--------- .../Taint/TaintIssuesSynchronizer.cs | 1 - 3 files changed, 487 insertions(+), 591 deletions(-) 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/TaintIssuesSynchronizerTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs index 0841d64578..1d97a8d78d 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs @@ -1,587 +1,484 @@ -// /* -// * 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 v8.6 or later"); -// logger.AssertPartialOutputStringExists($"Connected SonarQube 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 Moq; +using NSubstitute.ClearExtensions; +using NSubstitute.Core; +using NSubstitute.ExceptionExtensions; +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; +using SonarLint.VisualStudio.IssueVisualization.Security.Taint.TaintList; +using SonarLint.VisualStudio.SLCore.Core; +using SonarLint.VisualStudio.SLCore.Service.Taint; +using SonarLint.VisualStudio.SLCore.State; +using SonarLint.VisualStudio.TestInfrastructure; +using SonarQube.Client; + +namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint; + +[TestClass] +public class TaintIssuesSynchronizerTests +{ + private ITaintStore taintStore; + private ISLCoreServiceProvider slCoreServiceProvider; + private ITaintIssueToIssueVisualizationConverter taintIssueToIssueVisualizationConverter; + 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 releaseAsyncLock; + private static readonly ConfigurationScope None = null; + private static readonly ConfigurationScope Standalone = new("some id", null, null, "some path"); + private static readonly ConfigurationScope ConnectedWithUninitializedRoot = new("some id", "some connection", "some project"); + private static readonly ConfigurationScope ConnectedReady = new("some id", "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(); + var threadHandlingMock = 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, + taintIssueToIssueVisualizationConverter, + toolWindowService, + vsUiServiceOperation, + threadHandlingMock, + asyncLockFactory, + loggerMock); + taintStore.ReceivedCalls().Should().BeEmpty(); + slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); + taintIssueToIssueVisualizationConverter.ReceivedCalls().Should().BeEmpty(); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + vsUiServiceOperation.ReceivedCalls().Should().BeEmpty(); + threadHandlingMock.ReceivedCalls().Should().BeEmpty(); + asyncLockFactory.Received(1).Create(); + loggerMock.ReceivedCalls().Should().BeEmpty(); + } + + [TestInitialize] + public void TestInitialize() + { + taintStore = Substitute.For(); + slCoreServiceProvider = Substitute.For(); + taintIssueToIssueVisualizationConverter = Substitute.For(); + toolWindowService = Substitute.For(); + vsMonitorSelection = Substitute.For(); + vsUiServiceOperation = CreateDefaultServiceOperation(vsMonitorSelection); + threadHandling = new NoOpThreadHandler(); + asyncLock = Substitute.For(); + releaseAsyncLock = Substitute.For(); + asyncLockFactory = CreateDefaultAsyncLockFactory(asyncLock, releaseAsyncLock); + logger = new TestLogger(); + // 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, + taintIssueToIssueVisualizationConverter, + toolWindowService, + vsUiServiceOperation, + threadHandling, + asyncLockFactory, + logger); + } + + private static IAsyncLockFactory CreateDefaultAsyncLockFactory(IAsyncLock asyncLock, IReleaseAsyncLock release) + { + var factory = Substitute.For(); + factory.Create().Returns(asyncLock); + asyncLock.AcquireAsync().Returns(release); + return factory; + } + + [TestMethod] + public async Task SynchronizeWithServer_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"); + } + + [TestMethod] + public async Task SynchronizeWithServer_CriticalException_ExceptionNotCaught() + { + slCoreServiceProvider.TryGetTransientService(out Arg.Any()).Throws(new DivideByZeroException()); + + var act = async () => await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); + + await act.Should().ThrowAsync(); + } + + [TestMethod] + public async Task SynchronizeWithServer_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(); + taintIssueToIssueVisualizationConverter.ReceivedCalls().Should().BeEmpty(); + toolWindowService.ReceivedCalls().Should().BeEmpty(); + } + // + // [TestMethod] + // public async Task SynchronizeWithServer_SonarQubeServerNotYetConnected_StoreAndUIContextCleared() + // { + // var sonarService = CreateSonarService(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_Connected, + // taintStore.Object, + // 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(true, ServerType.SonarQube, versionString); + // var logger = new TestLogger(); + // + // const uint cookie = 999; + // var monitor = CreateMonitorSelectionMock(cookie); + // var taintStore = new Mock(); + // + // var testSubject = CreateTestSubject( + // BindingConfig_Connected, + // taintStore.Object, + // sonarService: sonarQubeServer.Object, + // vsMonitor: monitor.Object, + // logger: logger); + // + // await testSubject.SynchronizeWithServer(); + // + // logger.AssertPartialOutputStringExists("requires SonarQube v8.6 or later"); + // logger.AssertPartialOutputStringExists($"Connected SonarQube 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(true, serverType, serverVersion); + // + // var bindingConfig = CreateBindingConfig(SonarLintMode.Connected, "keyXXX"); + // var serverBranchProvider = CreateServerBranchProvider("branchXXX"); + // SetupTaintIssues(sonarServer, "keyXXX", "branchXXX"); + // + // var testSubject = CreateTestSubject( + // 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(); + // 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, + // taintStore.Object, + // 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(); + // SetupTaintIssues(sonarQubeService, "xxx_project-key", "branch-XYZ"); + // + // var testSubject = CreateTestSubject( + // 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, + // taintStore.Object, + // 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, + // 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 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(It.IsAny>())) + .Do(call => call.Arg>().Invoke(svcToPassToCallback)); + + return serviceOp; + } + + 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 void CheckUIContextIsCleared(uint expectedCookie) => CheckUIContextUpdated(expectedCookie, 0); + + private void CheckUIContextIsSet(Mock monitorMock, uint expectedCookie) => CheckUIContextUpdated(expectedCookie, 1); + + private void CheckUIContextUpdated(uint expectedCookie, int expectedState) => + vsMonitorSelection.Received(1).SetCmdUIContext(expectedCookie, expectedState); + + 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() => taintStore.Received(1).Set([], null); +} diff --git a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs index 31ffecff04..d481468aba 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -57,7 +57,6 @@ public TaintIssuesSynchronizer( ISLCoreServiceProvider slCoreServiceProvider, ITaintIssueToIssueVisualizationConverter converter, IToolWindowService toolWindowService, - IStatefulServerBranchProvider serverBranchProvider, IVsUIServiceOperation vSServiceOperation, IThreadHandling threadHandling, IAsyncLockFactory asyncLockFactory, From 96e1b8a1b83dc6c380994d93c7630f697d950f9a Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 12 Nov 2024 10:42:07 +0100 Subject: [PATCH 3/9] TaintStoreTests fix & updates to TaintStore --- .../Taint/TaintStoreTests.cs | 785 +++++++++--------- .../Taint/TaintIssuesSynchronizer.cs | 2 +- src/IssueViz.Security/Taint/TaintStore.cs | 11 +- 3 files changed, 403 insertions(+), 395 deletions(-) diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs index 585b5953d2..31c810b3bb 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs @@ -1,391 +1,394 @@ -// /* -// * 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.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 -// { -// [TestClass] -// public class TaintStoreTests -// { -// [TestMethod] -// public void MefCtor_CheckExports() -// { -// var batch = new CompositionBatch(); -// -// 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); -// -// storeImport.Import.Should().NotBeNull(); -// issuesStoreImport.Import.Should().NotBeNull(); -// -// 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)); -// -// var issuesList1 = testSubject.GetAll(); -// testSubject.Add(SetupIssueViz()); -// var issuesList2 = testSubject.GetAll(); -// -// issuesList1.Count.Should().Be(2); -// issuesList2.Count.Should().Be(3); -// } -// -// [TestMethod] -// public void Set_NullCollection_ArgumentNullException() -// { -// var testSubject = CreateTestSubject(); -// -// Action act = () => testSubject.Set(null, null); -// -// act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); -// } -// -// [TestMethod] -// public void Set_NoSubscribersToIssuesChangedEvent_NoException() -// { -// var testSubject = CreateTestSubject(); -// -// Action act = () => testSubject.Set(new[] { SetupIssueViz() }, null); -// -// act.Should().NotThrow(); -// } -// -// [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); -// } -// -// [TestMethod] -// public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() -// { -// var testSubject = CreateTestSubject(); -// -// var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; -// testSubject.Set(oldItems, null); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; -// -// testSubject.Set(Enumerable.Empty(), null); -// -// testSubject.GetAll().Should().BeEmpty(); -// callCount.Should().Be(1); -// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); -// suppliedArgs.AddedIssues.Should().BeEmpty(); -// } -// -// [TestMethod] -// public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() -// { -// var testSubject = CreateTestSubject(); -// -// var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; -// testSubject.Set(oldItems, null); -// -// 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().BeEquivalentTo(oldItems); -// suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); -// } -// -// [TestMethod] -// public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() -// { -// var testSubject = CreateTestSubject(); -// -// var issueViz1 = SetupIssueViz("key1"); -// var issueViz2 = SetupIssueViz("key2"); -// var issueViz2NewObject = SetupIssueViz("key2"); -// var issueViz3 = SetupIssueViz("key3"); -// -// var oldItems = new[] { issueViz1, issueViz2 }; -// testSubject.Set(oldItems, null); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; -// -// var newItems = new[] { issueViz2NewObject, issueViz3}; -// testSubject.Set(newItems, null); -// -// 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.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(); -// -// Action act = () => testSubject.Remove(null); -// -// act.Should().Throw().And.ParamName.Should().Be("issueKey"); -// } -// -// [TestMethod] -// public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() -// { -// var testSubject = CreateTestSubject(); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; -// -// testSubject.Remove("some unknown key"); -// -// callCount.Should().Be(0); -// testSubject.GetAll().Should().BeEmpty(); -// } -// -// [TestMethod] -// public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() -// { -// var existingIssue = SetupIssueViz("key1"); -// -// var testSubject = CreateTestSubject(); -// testSubject.Set(new[] { existingIssue }, null); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; -// -// testSubject.Remove("some unknown key"); -// -// 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; }; -// -// testSubject.Remove("key2"); -// -// callCount.Should().Be(1); -// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); -// suppliedArgs.AddedIssues.Should().BeEmpty(); -// testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); -// } -// -// [TestMethod] -// public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() -// { -// var existingIssue1 = SetupIssueViz("key1"); -// var existingIssue2 = SetupIssueViz("key1"); -// var existingIssue3 = SetupIssueViz("key1"); -// -// var testSubject = CreateTestSubject(); -// testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, null); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; -// -// testSubject.Remove("key1"); -// -// callCount.Should().Be(1); -// suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); -// suppliedArgs.AddedIssues.Should().BeEmpty(); -// testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); -// } -// -// [TestMethod] -// public void Add_IssueIsNull_ArgumentNullException() -// { -// var testSubject = CreateTestSubject(); -// -// Action act = () => testSubject.Add(null); -// -// act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); -// } -// -// [TestMethod] -// public void Add_NoAnalysisInformation_IssueIgnoredAndNoEventIsRaised() -// { -// var existingIssue = SetupIssueViz("key1"); -// -// var testSubject = CreateTestSubject(); -// testSubject.Set(new[] { existingIssue}, null); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; -// -// testSubject.Add(SetupIssueViz()); -// -// callCount.Should().Be(0); -// testSubject.GetAll().Should().BeEquivalentTo(existingIssue); -// } -// -// [TestMethod] -// public void Add_HasAnalysisInformation_IssueAddedAndEventIsRaised() -// { -// var analysisInformation = new AnalysisInformation("some branch", DateTimeOffset.Now); -// var existingIssue = SetupIssueViz("key1"); -// -// var testSubject = CreateTestSubject(); -// testSubject.Set(new[] { existingIssue }, analysisInformation); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; -// -// 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); -// } -// -// [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); -// -// var callCount = 0; -// IssuesChangedEventArgs suppliedArgs = null; -// testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; -// -// var newIssue = SetupIssueViz(issueKey); -// testSubject.Add(newIssue); -// -// callCount.Should().Be(0); -// testSubject.GetAll().Should().BeEquivalentTo(existingIssue); -// } -// -// private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) -// { -// issueKey ??= Guid.NewGuid().ToString(); -// -// var taintIssue = new Mock(); -// taintIssue.Setup(x => x.IssueKey).Returns(issueKey); -// -// var issueViz = new Mock(); -// issueViz.Setup(x => x.Issue).Returns(taintIssue.Object); -// -// return issueViz.Object; -// } -// -// private ITaintStore CreateTestSubject() -// { -// return new TaintStore(); -// } -// } -// } +/* + * 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 System.ComponentModel.Composition.Hosting; +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 +{ + [TestClass] + public class TaintStoreTests + { + [TestMethod] + public void MefCtor_CheckExports() + { + var batch = new CompositionBatch(); + + 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); + + storeImport.Import.Should().NotBeNull(); + issuesStoreImport.Import.Should().NotBeNull(); + + storeImport.Import.Should().BeSameAs(issuesStoreImport.Import); + } + + [TestMethod] + public void GetAll_ReturnsImmutableInstance() + { + var testSubject = CreateTestSubject(); + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "config scope"); + + var issuesList1 = testSubject.GetAll(); + testSubject.Add(SetupIssueViz()); + var issuesList2 = testSubject.GetAll(); + + issuesList1.Count.Should().Be(2); + issuesList2.Count.Should().Be(3); + } + + [TestMethod] + public void Set_NullCollection_ArgumentNullException() + { + var testSubject = CreateTestSubject(); + + Action act = () => testSubject.Set(null, null); + + act.Should().Throw().And.ParamName.Should().Be("issueVisualizations"); + } + + [TestMethod] + public void Set_NoSubscribersToIssuesChangedEvent_NoException() + { + var testSubject = CreateTestSubject(); + + Action act = () => testSubject.Set(new[] { SetupIssueViz() }, "some config scope"); + + act.Should().NotThrow(); + } + + [TestMethod] + public void Set_NoPreviousItems_NoNewItems_CollectionChangedAndEventRaised() + { + var testSubject = CreateTestSubject(); + + var callCount = 0; + testSubject.IssuesChanged += (sender, args) => { callCount++; }; + + testSubject.Set([], 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, "some config scope"); + + testSubject.GetAll().Should().BeEquivalentTo(newItems); + callCount.Should().Be(1); + suppliedArgs.RemovedIssues.Should().BeEmpty(); + suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); + } + + [TestMethod] + public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() + { + var testSubject = CreateTestSubject(); + + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + + testSubject.Set([], "some config scope"); + + testSubject.GetAll().Should().BeEmpty(); + callCount.Should().Be(1); + suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); + suppliedArgs.AddedIssues.Should().BeEmpty(); + } + + [TestMethod] + public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() + { + var testSubject = CreateTestSubject(); + + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + + var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(newItems, "some config scope"); + + testSubject.GetAll().Should().BeEquivalentTo(newItems); + callCount.Should().Be(1); + suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); + suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); + } + + [TestMethod] + public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() + { + var testSubject = CreateTestSubject(); + + var issueViz1 = SetupIssueViz("key1"); + var issueViz2 = SetupIssueViz("key2"); + var issueViz2NewObject = SetupIssueViz("key2"); + var issueViz3 = SetupIssueViz("key3"); + + var oldItems = new[] { issueViz1, issueViz2 }; + testSubject.Set(oldItems, "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + + var newItems = new[] { issueViz2NewObject, issueViz3}; + testSubject.Set(newItems, "some config scope"); + + testSubject.GetAll().Should().BeEquivalentTo(newItems); + callCount.Should().Be(1); + suppliedArgs.RemovedIssues.Should().BeEquivalentTo(issueViz1); + suppliedArgs.AddedIssues.Should().BeEquivalentTo(issueViz3); + } + + [TestMethod] + public void Set_HasItems_NoConfigScope_Throws() + { + var testSubject = CreateTestSubject(); + var issueViz1 = SetupIssueViz("key1"); + + var act = () => testSubject.Set([issueViz1], null); + + act.Should().Throw().And.ParamName.Should().Be("newConfigurationScope"); + } + + [TestMethod] + public void ConfigScope_NoInformation_ReturnsNull() + { + var testSubject = CreateTestSubject(); + testSubject.Set([], null); + + var result = testSubject.ConfigurationScope; + result.Should().BeNull(); + } + + [TestMethod] + public void ConfigScope_HasInformation_ReturnsInformation() + { + const string newConfigurationScope = "some config scope"; + + var testSubject = CreateTestSubject(); + testSubject.Set([], newConfigurationScope); + + var result = testSubject.ConfigurationScope; + result.Should().BeSameAs(newConfigurationScope); + } + + [TestMethod] + public void Remove_IssueKeyIsNull_ArgumentNullException() + { + var testSubject = CreateTestSubject(); + + Action act = () => testSubject.Remove(null); + + act.Should().Throw().And.ParamName.Should().Be("issueKey"); + } + + [TestMethod] + public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() + { + var testSubject = CreateTestSubject(); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + + testSubject.Remove("some unknown key"); + + callCount.Should().Be(0); + testSubject.GetAll().Should().BeEmpty(); + } + + [TestMethod] + public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() + { + var existingIssue = SetupIssueViz("key1"); + + var testSubject = CreateTestSubject(); + testSubject.Set(new[] { existingIssue }, "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + + testSubject.Remove("some unknown key"); + + 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}, "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + + testSubject.Remove("key2"); + + callCount.Should().Be(1); + suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); + suppliedArgs.AddedIssues.Should().BeEmpty(); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); + } + + [TestMethod] + public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() + { + var existingIssue1 = SetupIssueViz("key1"); + var existingIssue2 = SetupIssueViz("key1"); + var existingIssue3 = SetupIssueViz("key1"); + + var testSubject = CreateTestSubject(); + testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + + testSubject.Remove("key1"); + + callCount.Should().Be(1); + suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); + suppliedArgs.AddedIssues.Should().BeEmpty(); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); + } + + [TestMethod] + public void Add_IssueIsNull_ArgumentNullException() + { + var testSubject = CreateTestSubject(); + + Action act = () => testSubject.Add(null); + + act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); + } + + [TestMethod] + public void Add_NoConfigScope_IssueIgnoredAndNoEventIsRaised() + { + var testSubject = CreateTestSubject(); + testSubject.Set([], null); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + + testSubject.Add(SetupIssueViz()); + + callCount.Should().Be(0); + testSubject.GetAll().Should().BeEmpty(); + } + + [TestMethod] + public void Add_HasConfigScope_IssueAddedAndEventIsRaised() + { + var existingIssue = SetupIssueViz("key1"); + + var testSubject = CreateTestSubject(); + testSubject.Set(new[] { existingIssue }, "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + + 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); + } + + [TestMethod] + public void Add_DuplicateIssue_IssueIgnoredAndNoEventIsRaised() + { + var issueKey = "key1"; + var existingIssue = SetupIssueViz(issueKey); + + var testSubject = CreateTestSubject(); + testSubject.Set([existingIssue], "some config scope"); + + var callCount = 0; + IssuesChangedEventArgs suppliedArgs = null; + testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + + var newIssue = SetupIssueViz(issueKey); + testSubject.Add(newIssue); + + callCount.Should().Be(0); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue); + } + + private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) + { + issueKey ??= Guid.NewGuid().ToString(); + + var taintIssue = new Mock(); + taintIssue.Setup(x => x.IssueKey).Returns(issueKey); + + var issueViz = new Mock(); + issueViz.Setup(x => x.Issue).Returns(taintIssue.Object); + + return issueViz.Object; + } + + private ITaintStore CreateTestSubject() + { + return new TaintStore(); + } + } +} diff --git a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs index d481468aba..37d48deff9 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -99,7 +99,7 @@ private async Task PerformSynchronizationInternalAsync(ConfigurationScope config 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)), configurationScope.Id); + taintStore.Set(taintsResponse.taintVulnerabilities.Select(x => converter.Convert(x, configurationScope.RootPath)).ToArray(), configurationScope.Id); HandleUIContextUpdate(taintsResponse); } diff --git a/src/IssueViz.Security/Taint/TaintStore.cs b/src/IssueViz.Security/Taint/TaintStore.cs index 77b2f5efb0..219053250b 100644 --- a/src/IssueViz.Security/Taint/TaintStore.cs +++ b/src/IssueViz.Security/Taint/TaintStore.cs @@ -31,7 +31,7 @@ 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, string newConfigurationScope); + void Set(IReadOnlyCollection issueVisualizations, string newConfigurationScope); string ConfigurationScope { get; } @@ -122,18 +122,23 @@ public void Remove(string issueKey) } } - public void Set(IEnumerable issueVisualizations, string newConfigurationScope) + public void Set(IReadOnlyCollection issueVisualizations, string newConfigurationScope) { if (issueVisualizations == null) { throw new ArgumentNullException(nameof(issueVisualizations)); } + if (issueVisualizations.Count > 0 && newConfigurationScope == null) + { + throw new ArgumentNullException(nameof(newConfigurationScope)); + } + lock (locker) { - configurationScope = newConfigurationScope; var oldIssues = taintVulnerabilities; taintVulnerabilities = issueVisualizations.ToList(); + configurationScope = newConfigurationScope; var removedIssues = oldIssues.Except(taintVulnerabilities, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); var addedIssues = taintVulnerabilities.Except(oldIssues, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); From 39e24c99d8903f20abb9ff931bb0ab9d6ceda442 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 12 Nov 2024 10:57:00 +0100 Subject: [PATCH 4/9] TaintStoreTests CAYC --- .../Taint/TaintStoreTests.cs | 551 ++++++++---------- 1 file changed, 259 insertions(+), 292 deletions(-) diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs index 31c810b3bb..bec2492be3 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs @@ -20,375 +20,342 @@ using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; -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, "config scope"); + [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"); + } - Action act = () => testSubject.Set(new[] { SetupIssueViz() }, "some config scope"); - - act.Should().NotThrow(); - } + [TestMethod] + public void Set_NoSubscribersToIssuesChangedEvent_NoException() + { + Action act = () => testSubject.Set(new[] { SetupIssueViz() }, "some config scope"); - [TestMethod] - public void Set_NoPreviousItems_NoNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - testSubject.IssuesChanged += (sender, args) => { callCount++; }; + act.Should().NotThrow(); + } - testSubject.Set([], null); + [TestMethod] + public void Set_NoPreviousItems_NoNewItems_CollectionChangedAndEventRaised() + { + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - 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, "some config scope"); - - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEmpty(); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); - } + testSubject.Set([], null); - [TestMethod] - public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); - - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, "some config scope"); + testSubject.GetAll().Should().BeEmpty(); + eventHandlerMock.ReceivedWithAnyArgs(1).Invoke(default, default); + } - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + [TestMethod] + public void Set_NoPreviousItems_HasNewItems_CollectionChangedAndEventRaised() + { + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - testSubject.Set([], "some config scope"); - - testSubject.GetAll().Should().BeEmpty(); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEmpty(); - } + var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(newItems, "some config scope"); - [TestMethod] - public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); + 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); + } - var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(oldItems, "some config scope"); + [TestMethod] + public void Set_HasPreviousItems_NoNewItems_CollectionChangedAndEventRaised() + { + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "some config scope"); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (sender, args) => { callCount++; suppliedArgs = args; }; + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var newItems = new[] { SetupIssueViz(), SetupIssueViz() }; - testSubject.Set(newItems, "some config scope"); + testSubject.Set([], "some config scope"); - testSubject.GetAll().Should().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(oldItems); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newItems); - } + 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(); + } - [TestMethod] - public void Set_HasPreviousItems_HasSomeNewItems_CollectionChangedAndEventRaised() - { - var testSubject = CreateTestSubject(); + [TestMethod] + public void Set_HasPreviousItems_HasNewItems_CollectionChangedAndEventRaised() + { + var oldItems = new[] { SetupIssueViz(), SetupIssueViz() }; + testSubject.Set(oldItems, "some config scope"); - var issueViz1 = SetupIssueViz("key1"); - var issueViz2 = SetupIssueViz("key2"); - var issueViz2NewObject = SetupIssueViz("key2"); - var issueViz3 = SetupIssueViz("key3"); + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var oldItems = new[] { issueViz1, issueViz2 }; - testSubject.Set(oldItems, "some config scope"); + 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); + } - var newItems = new[] { issueViz2NewObject, issueViz3}; - testSubject.Set(newItems, "some config scope"); + [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().BeEquivalentTo(newItems); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(issueViz1); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(issueViz3); - } + var oldItems = new[] { issueViz1, issueViz2 }; + testSubject.Set(oldItems, "some config scope"); - [TestMethod] - public void Set_HasItems_NoConfigScope_Throws() - { - var testSubject = CreateTestSubject(); - var issueViz1 = SetupIssueViz("key1"); + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var act = () => testSubject.Set([issueViz1], null); + var newItems = new[] { issueViz2NewObject, issueViz3}; + testSubject.Set(newItems, "some config scope"); - act.Should().Throw().And.ParamName.Should().Be("newConfigurationScope"); - } - - [TestMethod] - public void ConfigScope_NoInformation_ReturnsNull() - { - var testSubject = CreateTestSubject(); - testSubject.Set([], null); - - var result = testSubject.ConfigurationScope; - result.Should().BeNull(); - } - - [TestMethod] - public void ConfigScope_HasInformation_ReturnsInformation() - { - const string newConfigurationScope = "some config scope"; - - var testSubject = CreateTestSubject(); - testSubject.Set([], newConfigurationScope); - - var result = testSubject.ConfigurationScope; - result.Should().BeSameAs(newConfigurationScope); - } + 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); + } - [TestMethod] - public void Remove_IssueKeyIsNull_ArgumentNullException() - { - var testSubject = CreateTestSubject(); + [TestMethod] + public void Set_HasItems_NoConfigScope_Throws() + { + var issueViz1 = SetupIssueViz("key1"); - Action act = () => testSubject.Remove(null); + var act = () => testSubject.Set([issueViz1], null); - act.Should().Throw().And.ParamName.Should().Be("issueKey"); - } + act.Should().Throw().And.ParamName.Should().Be("newConfigurationScope"); + } - [TestMethod] - public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() - { - var testSubject = CreateTestSubject(); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + [TestMethod] + public void ConfigScope_NoInformation_ReturnsNull() + { + testSubject.Set([], null); - testSubject.Remove("some unknown key"); + var result = testSubject.ConfigurationScope; + result.Should().BeNull(); + } - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEmpty(); - } + [TestMethod] + public void ConfigScope_HasInformation_ReturnsInformation() + { + const string newConfigurationScope = "some config scope"; - [TestMethod] - public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); + testSubject.Set([], newConfigurationScope); - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, "some config scope"); - - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; - - testSubject.Remove("some unknown key"); - - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } + var result = testSubject.ConfigurationScope; + result.Should().BeSameAs(newConfigurationScope); + } - [TestMethod] - public void Remove_IssueFound_IssueIsRemovedAndEventIsRaised() - { - var existingIssue1 = SetupIssueViz("key1"); - var existingIssue2 = SetupIssueViz("key2"); - var existingIssue3 = SetupIssueViz("key3"); + [TestMethod] + public void Remove_IssueKeyIsNull_ArgumentNullException() + { + Action act = () => testSubject.Remove(null); - var testSubject = CreateTestSubject(); - testSubject.Set(new[] {existingIssue1, existingIssue2, existingIssue3}, "some config scope"); + act.Should().Throw().And.ParamName.Should().Be("issueKey"); + } - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + [TestMethod] + public void Remove_IssueNotFound_NoIssuesInList_NoEventIsRaised() + { + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - testSubject.Remove("key2"); + testSubject.Remove("some unknown key"); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue2); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue1, existingIssue3); - } + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEmpty(); + } - [TestMethod] - public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() - { - var existingIssue1 = SetupIssueViz("key1"); - var existingIssue2 = SetupIssueViz("key1"); - var existingIssue3 = SetupIssueViz("key1"); + [TestMethod] + public void Remove_IssueNotFound_NoIssueWithThisId_NoEventIsRaised() + { + var existingIssue = SetupIssueViz("key1"); - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue1, existingIssue2, existingIssue3 }, "some config scope"); + testSubject.Set(new[] { existingIssue }, "some config scope"); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - testSubject.Remove("key1"); + testSubject.Remove("some unknown key"); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEquivalentTo(existingIssue1); - suppliedArgs.AddedIssues.Should().BeEmpty(); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue2, existingIssue3); - } + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue); + } - [TestMethod] - public void Add_IssueIsNull_ArgumentNullException() - { - var testSubject = CreateTestSubject(); + [TestMethod] + public void Remove_IssueFound_IssueIsRemovedAndEventIsRaised() + { + var existingIssue1 = SetupIssueViz("key1"); + var existingIssue2 = SetupIssueViz("key2"); + var existingIssue3 = SetupIssueViz("key3"); - Action act = () => testSubject.Add(null); + testSubject.Set([existingIssue1, existingIssue2, existingIssue3], "some config scope"); - act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); - } + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - [TestMethod] - public void Add_NoConfigScope_IssueIgnoredAndNoEventIsRaised() - { - var testSubject = CreateTestSubject(); - testSubject.Set([], null); + testSubject.Remove("key2"); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + 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); + } - testSubject.Add(SetupIssueViz()); + [TestMethod] + public void Remove_MultipleIssuesFoundWithSameId_FirstIssueIsRemovedAndEventIsRaised() + { + var existingIssue1 = SetupIssueViz("key1"); + var existingIssue2 = SetupIssueViz("key1"); + var existingIssue3 = SetupIssueViz("key1"); - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEmpty(); - } + testSubject.Set([existingIssue1, existingIssue2, existingIssue3], "some config scope"); - [TestMethod] - public void Add_HasConfigScope_IssueAddedAndEventIsRaised() - { - var existingIssue = SetupIssueViz("key1"); + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; - var testSubject = CreateTestSubject(); - testSubject.Set(new[] { existingIssue }, "some config scope"); + testSubject.Remove("key1"); - var callCount = 0; - IssuesChangedEventArgs suppliedArgs = null; - testSubject.IssuesChanged += (_, args) => { callCount++; suppliedArgs = args; }; + 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); + } - var newIssue = SetupIssueViz(); - testSubject.Add(newIssue); + [TestMethod] + public void Add_IssueIsNull_ArgumentNullException() + { + Action act = () => testSubject.Add(null); - callCount.Should().Be(1); - suppliedArgs.RemovedIssues.Should().BeEmpty(); - suppliedArgs.AddedIssues.Should().BeEquivalentTo(newIssue); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue, newIssue); - } + act.Should().Throw().And.ParamName.Should().Be("issueVisualization"); + } - [TestMethod] - public void Add_DuplicateIssue_IssueIgnoredAndNoEventIsRaised() - { - var issueKey = "key1"; - var existingIssue = SetupIssueViz(issueKey); - - var testSubject = CreateTestSubject(); - testSubject.Set([existingIssue], "some config scope"); + [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; - var newIssue = SetupIssueViz(issueKey); - testSubject.Add(newIssue); + testSubject.Add(SetupIssueViz()); - callCount.Should().Be(0); - testSubject.GetAll().Should().BeEquivalentTo(existingIssue); - } + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEmpty(); + } - private IAnalysisIssueVisualization SetupIssueViz(string issueKey = null) - { - issueKey ??= Guid.NewGuid().ToString(); + [TestMethod] + public void Add_HasConfigScope_IssueAddedAndEventIsRaised() + { + var existingIssue = SetupIssueViz("key1"); - var taintIssue = new Mock(); - taintIssue.Setup(x => x.IssueKey).Returns(issueKey); + testSubject.Set([existingIssue], "some config scope"); + + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; + + var newIssue = SetupIssueViz(); + testSubject.Add(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 issueKey = "key1"; + var existingIssue = SetupIssueViz(issueKey); + + testSubject.Set([existingIssue], "some config scope"); + + var eventHandlerMock = Substitute.For>(); + testSubject.IssuesChanged += eventHandlerMock; + + var newIssue = SetupIssueViz(issueKey); + testSubject.Add(newIssue); + + eventHandlerMock.DidNotReceiveWithAnyArgs().Invoke(default, default); + testSubject.GetAll().Should().BeEquivalentTo(existingIssue); + } + + 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; } } From 36d81eda13015ee02fd96468eb594ebe822a1298 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 12 Nov 2024 13:57:01 +0100 Subject: [PATCH 5/9] Update TaintIssuesSynchronizer & TaintIssuesSynchronizerTests --- .../Taint/TaintIssuesSynchronizerTests.cs | 494 +++++++----------- .../Taint/TaintIssuesSynchronizer.cs | 54 +- .../Taint/TaintResources.Designer.cs | 6 +- .../Taint/TaintResources.resx | 4 +- 4 files changed, 224 insertions(+), 334 deletions(-) diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs index 1d97a8d78d..c116e4fee6 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs @@ -24,6 +24,7 @@ using NSubstitute.ClearExtensions; using NSubstitute.Core; using NSubstitute.ExceptionExtensions; +using NSubstitute.ReceivedExtensions; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Binding; using SonarLint.VisualStudio.Core.Synchronization; @@ -31,7 +32,10 @@ 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.Protocol; +using SonarLint.VisualStudio.SLCore.Service.Rules.Models; using SonarLint.VisualStudio.SLCore.Service.Taint; using SonarLint.VisualStudio.SLCore.State; using SonarLint.VisualStudio.TestInfrastructure; @@ -44,7 +48,8 @@ public class TaintIssuesSynchronizerTests { private ITaintStore taintStore; private ISLCoreServiceProvider slCoreServiceProvider; - private ITaintIssueToIssueVisualizationConverter taintIssueToIssueVisualizationConverter; + private ITaintVulnerabilityTrackingSlCoreService taintService; + private ITaintIssueToIssueVisualizationConverter converter; private IToolWindowService toolWindowService; private IVsUIServiceOperation vsUiServiceOperation; private IVsMonitorSelection vsMonitorSelection; @@ -53,11 +58,11 @@ public class TaintIssuesSynchronizerTests private IAsyncLock asyncLock; private TestLogger logger; private TaintIssuesSynchronizer testSubject; - private IReleaseAsyncLock releaseAsyncLock; + private IReleaseAsyncLock asyncLockReleaser; private static readonly ConfigurationScope None = null; - private static readonly ConfigurationScope Standalone = new("some id", null, null, "some path"); - private static readonly ConfigurationScope ConnectedWithUninitializedRoot = new("some id", "some connection", "some project"); - private static readonly ConfigurationScope ConnectedReady = new("some id", "some connection", "some project", "some path"); + 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() => @@ -76,23 +81,22 @@ public void Ctor_DoesNotCallAnyServices() { asyncLockFactory.ClearSubstitute(); var loggerMock = Substitute.For(); - var threadHandlingMock = 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, - taintIssueToIssueVisualizationConverter, + converter, toolWindowService, vsUiServiceOperation, - threadHandlingMock, + threadHandling, asyncLockFactory, loggerMock); taintStore.ReceivedCalls().Should().BeEmpty(); slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); - taintIssueToIssueVisualizationConverter.ReceivedCalls().Should().BeEmpty(); + converter.ReceivedCalls().Should().BeEmpty(); toolWindowService.ReceivedCalls().Should().BeEmpty(); vsUiServiceOperation.ReceivedCalls().Should().BeEmpty(); - threadHandlingMock.ReceivedCalls().Should().BeEmpty(); + threadHandling.ReceivedCalls().Should().BeEmpty(); asyncLockFactory.Received(1).Create(); loggerMock.ReceivedCalls().Should().BeEmpty(); } @@ -101,21 +105,20 @@ public void Ctor_DoesNotCallAnyServices() public void TestInitialize() { taintStore = Substitute.For(); - slCoreServiceProvider = Substitute.For(); - taintIssueToIssueVisualizationConverter = Substitute.For(); + taintService = Substitute.For(); + slCoreServiceProvider = CreateDefaultServiceProvider(taintService); + converter = Substitute.For(); toolWindowService = Substitute.For(); vsMonitorSelection = Substitute.For(); vsUiServiceOperation = CreateDefaultServiceOperation(vsMonitorSelection); - threadHandling = new NoOpThreadHandler(); + threadHandling = CreateDefaultThreadHandling(); asyncLock = Substitute.For(); - releaseAsyncLock = Substitute.For(); - asyncLockFactory = CreateDefaultAsyncLockFactory(asyncLock, releaseAsyncLock); + asyncLockReleaser = Substitute.For(); + asyncLockFactory = CreateDefaultAsyncLockFactory(asyncLock, asyncLockReleaser); logger = new TestLogger(); - // 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, - taintIssueToIssueVisualizationConverter, + converter, toolWindowService, vsUiServiceOperation, threadHandling, @@ -123,16 +126,8 @@ public void TestInitialize() logger); } - private static IAsyncLockFactory CreateDefaultAsyncLockFactory(IAsyncLock asyncLock, IReleaseAsyncLock release) - { - var factory = Substitute.For(); - factory.Create().Returns(asyncLock); - asyncLock.AcquireAsync().Returns(release); - return factory; - } - [TestMethod] - public async Task SynchronizeWithServer_NonCriticalException_UIContextAndStoreCleared() + public async Task UpdateTaintVulnerabilitiesAsync_NonCriticalException_UIContextAndStoreCleared() { slCoreServiceProvider.TryGetTransientService(out Arg.Any()).Throws(new Exception("this is a test")); @@ -145,20 +140,38 @@ public async Task SynchronizeWithServer_NonCriticalException_UIContextAndStoreCl CheckStoreIsCleared(); // CheckUIContextIsCleared(cookie); logger.AssertPartialOutputStringExists("this is a test"); + asyncLockReleaser.Received().Dispose(); } [TestMethod] - public async Task SynchronizeWithServer_CriticalException_ExceptionNotCaught() + 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 SynchronizeWithServer_StandaloneMode_StoreAndUIContextCleared() + 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); @@ -169,287 +182,93 @@ public async Task SynchronizeWithServer_StandaloneMode_StoreAndUIContextCleared( // CheckUIContextIsCleared(cookie); logger.AssertPartialOutputStringExists("not in connected mode"); slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); - taintIssueToIssueVisualizationConverter.ReceivedCalls().Should().BeEmpty(); toolWindowService.ReceivedCalls().Should().BeEmpty(); } - // - // [TestMethod] - // public async Task SynchronizeWithServer_SonarQubeServerNotYetConnected_StoreAndUIContextCleared() - // { - // var sonarService = CreateSonarService(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_Connected, - // taintStore.Object, - // 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(true, ServerType.SonarQube, versionString); - // var logger = new TestLogger(); - // - // const uint cookie = 999; - // var monitor = CreateMonitorSelectionMock(cookie); - // var taintStore = new Mock(); - // - // var testSubject = CreateTestSubject( - // BindingConfig_Connected, - // taintStore.Object, - // sonarService: sonarQubeServer.Object, - // vsMonitor: monitor.Object, - // logger: logger); - // - // await testSubject.SynchronizeWithServer(); - // - // logger.AssertPartialOutputStringExists("requires SonarQube v8.6 or later"); - // logger.AssertPartialOutputStringExists($"Connected SonarQube 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(true, serverType, serverVersion); - // - // var bindingConfig = CreateBindingConfig(SonarLintMode.Connected, "keyXXX"); - // var serverBranchProvider = CreateServerBranchProvider("branchXXX"); - // SetupTaintIssues(sonarServer, "keyXXX", "branchXXX"); - // - // var testSubject = CreateTestSubject( - // 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(); - // 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, - // taintStore.Object, - // 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(); - // SetupTaintIssues(sonarQubeService, "xxx_project-key", "branch-XYZ"); - // - // var testSubject = CreateTestSubject( - // 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, - // taintStore.Object, - // 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, - // 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 IVsUIServiceOperation CreateDefaultServiceOperation(IVsMonitorSelection svcToPassToCallback) + [TestMethod] + public async Task UpdateTaintVulnerabilitiesAsync_ConnectedModeConfigScope_NotReady_StoreAndUIContextCleared() { - var serviceOp = Substitute.For(); + const uint cookie = 123; + SetUpMonitorSelectionMock(cookie); - // Set up the mock to invoke the operation with the supplied VS service - serviceOp.When(x => x.Execute(It.IsAny>())) - .Do(call => call.Arg>().Invoke(svcToPassToCallback)); + await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedWithUninitializedRoot); - return serviceOp; + 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); + + // todo log + 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) @@ -462,23 +281,68 @@ private void SetUpMonitorSelectionMock(uint cookie) }); } - private void CheckUIContextIsCleared(uint expectedCookie) => CheckUIContextUpdated(expectedCookie, 0); + private static ISLCoreServiceProvider CreateDefaultServiceProvider(ITaintVulnerabilityTrackingSlCoreService taintService) + { + var slCoreServiceProvider = Substitute.For(); + slCoreServiceProvider.TryGetTransientService(out ITaintVulnerabilityTrackingSlCoreService _).Returns(call => + { + call[0] = taintService; + return true; + }); - private void CheckUIContextIsSet(Mock monitorMock, uint expectedCookie) => CheckUIContextUpdated(expectedCookie, 1); + return slCoreServiceProvider; + } - private void CheckUIContextUpdated(uint expectedCookie, int expectedState) => - vsMonitorSelection.Received(1).SetCmdUIContext(expectedCookie, expectedState); + 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(It.IsAny>())) + .Do(call => call.Arg>().Invoke(svcToPassToCallback)); - private static void CheckConnectedStatusIsChecked(Mock serviceMock) => serviceMock.Verify(x => x.GetServerInfo(), Times.Once); + return serviceOp; + } - private static void CheckIssuesAreFetched(Mock serviceMock, string projectKey, string branch) => - serviceMock.Verify(x => x.GetTaintVulnerabilitiesAsync(projectKey, branch, It.IsAny()), Times.Once); + private static IThreadHandling CreateDefaultThreadHandling() + { + var mock = Substitute.For(); + mock.RunOnBackgroundThread(Arg.Any>>()).Returns(call => call.Arg>>()()); + return mock; + } - private static void CheckIssuesAreNotFetched(Mock serviceMock) => - serviceMock.Verify(x => x.GetTaintVulnerabilitiesAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never); + 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 static void CheckToolWindowServiceIsCalled(Mock toolWindowServiceMock) => - toolWindowServiceMock.Verify(x => x.EnsureToolWindowExists(TaintToolWindow.ToolWindowId), Times.Once); + private void CheckToolWindowServiceIsCalled() => + toolWindowService.Received().EnsureToolWindowExists(TaintToolWindow.ToolWindowId); private void CheckStoreIsCleared() => taintStore.Received(1).Set([], null); } diff --git a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs index 37d48deff9..1ef878cd90 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -85,13 +85,15 @@ private async Task PerformSynchronizationInternalAsync(ConfigurationScope config { try { - if (IsStandalone(configurationScope) || !slCoreServiceProvider.TryGetTransientService(out ITaintVulnerabilityTrackingSlCoreService taintService)) + if (!IsConnectedModeConfigScope(configurationScope) + || !IsConfigScopeReady(configurationScope) + || !TryGetSLCoreService(out var taintService)) { HandleNoTaintIssues(); return; } - if (!IsConfigScopeReady(configurationScope) || IsAlreadyInitializedForConfigScope(configurationScope)) + if (IsAlreadyInitializedForConfigScope(configurationScope)) { return; } @@ -101,7 +103,7 @@ private async Task PerformSynchronizationInternalAsync(ConfigurationScope config taintStore.Set(taintsResponse.taintVulnerabilities.Select(x => converter.Convert(x, configurationScope.RootPath)).ToArray(), configurationScope.Id); - HandleUIContextUpdate(taintsResponse); + HandleUIContextUpdate(taintsResponse.taintVulnerabilities.Count); } catch (Exception ex) when (!ErrorHandler.IsCriticalException(ex)) { @@ -110,19 +112,39 @@ private async Task PerformSynchronizationInternalAsync(ConfigurationScope config } } - private static bool IsConfigScopeReady(ConfigurationScope configurationScope) => configurationScope.RootPath is not null; - - private bool IsAlreadyInitializedForConfigScope(ConfigurationScope configurationScope) => taintStore.ConfigurationScope == configurationScope.Id; + private bool TryGetSLCoreService(out ITaintVulnerabilityTrackingSlCoreService taintService) + { + var result = slCoreServiceProvider.TryGetTransientService(out taintService); + if (!result) + { + logger.WriteLine(TaintResources.Synchronizer_SLCoreNotReady); + } + return result; + } - private void HandleUIContextUpdate(ListAllTaintsResponse taintsResponse) + private bool IsConfigScopeReady(ConfigurationScope configurationScope) { - var hasTaintIssues = taintsResponse.taintVulnerabilities.Count > 0; + var isReady = configurationScope.RootPath is not null; + if (!isReady) + { + logger.LogVerbose("Configuration scope root hasn't been initialized."); + } + return isReady; + } - if (!hasTaintIssues) + private bool IsAlreadyInitializedForConfigScope(ConfigurationScope configurationScope) + { + var isAlreadyInitialized = taintStore.ConfigurationScope == configurationScope.Id; + if (!isAlreadyInitialized) { - UpdateTaintIssuesUIContext(false); + logger.LogVerbose("Taint store has already been initialized for current configuration scope"); } - else + return isAlreadyInitialized; + } + + private void HandleUIContextUpdate(int taintsCount) + { + if (taintsCount > 0) { UpdateTaintIssuesUIContext(true); @@ -131,17 +153,21 @@ private void HandleUIContextUpdate(ListAllTaintsResponse taintsResponse) // for more information. toolWindowService.EnsureToolWindowExists(TaintToolWindow.ToolWindowId); } + else + { + UpdateTaintIssuesUIContext(false); + } } - private bool IsStandalone(ConfigurationScope configurationScope) + private bool IsConnectedModeConfigScope(ConfigurationScope configurationScope) { if (configurationScope is { SonarProjectId: not null }) { - return false; + return true; } logger.WriteLine(TaintResources.Synchronizer_NotInConnectedMode); - return true; + return false; } private void HandleNoTaintIssues() diff --git a/src/IssueViz.Security/Taint/TaintResources.Designer.cs b/src/IssueViz.Security/Taint/TaintResources.Designer.cs index 1e52382a0d..fdabbf5bbf 100644 --- a/src/IssueViz.Security/Taint/TaintResources.Designer.cs +++ b/src/IssueViz.Security/Taint/TaintResources.Designer.cs @@ -87,11 +87,11 @@ internal static string Synchronizer_NumberOfServerIssues { } /// - /// 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: SonarLint Core 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); } } diff --git a/src/IssueViz.Security/Taint/TaintResources.resx b/src/IssueViz.Security/Taint/TaintResources.resx index 9cb2000913..49c82fb792 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: SonarLint Core backend is not available. [Taint] Fetched {0} taint vulnerabilities. From c7f62402222e64492a2338bf36ffc96b223a4ba8 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 12 Nov 2024 13:57:33 +0100 Subject: [PATCH 6/9] Move TaintStore notifications invocation outside the synchronization blocks --- src/IssueViz.Security/Taint/TaintStore.cs | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/IssueViz.Security/Taint/TaintStore.cs b/src/IssueViz.Security/Taint/TaintStore.cs index 219053250b..35db00dd10 100644 --- a/src/IssueViz.Security/Taint/TaintStore.cs +++ b/src/IssueViz.Security/Taint/TaintStore.cs @@ -89,8 +89,9 @@ public void Add(IAnalysisIssueVisualization issueVisualization) taintVulnerabilities.Add(issueVisualization); - NotifyIssuesChanged(Array.Empty(), new[] { issueVisualization }); } + + NotifyIssuesChanged([], [issueVisualization]); } public void Remove(string issueKey) @@ -100,6 +101,8 @@ public void Remove(string issueKey) throw new ArgumentNullException(nameof(issueKey)); } + IAnalysisIssueVisualization valueToRemove; + lock (locker) { if (configurationScope == null) @@ -115,11 +118,12 @@ 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(IReadOnlyCollection issueVisualizations, string newConfigurationScope) @@ -134,17 +138,21 @@ public void Set(IReadOnlyCollection issueVisualizat throw new ArgumentNullException(nameof(newConfigurationScope)); } + IAnalysisIssueVisualization[] removedIssues; + IAnalysisIssueVisualization[] addedIssues; + lock (locker) { var oldIssues = taintVulnerabilities; taintVulnerabilities = issueVisualizations.ToList(); configurationScope = newConfigurationScope; - var removedIssues = oldIssues.Except(taintVulnerabilities, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); - var addedIssues = taintVulnerabilities.Except(oldIssues, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); - NotifyIssuesChanged(removedIssues, addedIssues); + removedIssues = oldIssues.Except(taintVulnerabilities, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); + addedIssues = taintVulnerabilities.Except(oldIssues, TaintAnalysisIssueVisualizationByIssueKeyEqualityComparer.Instance).ToArray(); } + + NotifyIssuesChanged(removedIssues, addedIssues); } public string ConfigurationScope From e91b64a1720a9dced06d914ebd4e6485e8ccf067 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 12 Nov 2024 14:37:41 +0100 Subject: [PATCH 7/9] revert todo --- src/Core/IFolderWorkspaceMonitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/IFolderWorkspaceMonitor.cs b/src/Core/IFolderWorkspaceMonitor.cs index 080f08d883..ce5a2f8a94 100644 --- a/src/Core/IFolderWorkspaceMonitor.cs +++ b/src/Core/IFolderWorkspaceMonitor.cs @@ -34,6 +34,6 @@ public interface IFolderWorkspaceMonitor /// We need this event since certain parts of our code, i.e. , /// rely on VsHierarchy being initialized by the time they're called. /// - event EventHandler FolderWorkspaceInitialized; // todo + event EventHandler FolderWorkspaceInitialized; } } From e64f8fdea71591678fefa90e6614756274e528c4 Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Tue, 12 Nov 2024 15:08:30 +0100 Subject: [PATCH 8/9] Fix ui context mock setup --- .../Taint/TaintIssuesSynchronizerTests.cs | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs index c116e4fee6..0db01427a7 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs @@ -138,7 +138,7 @@ public async Task UpdateTaintVulnerabilitiesAsync_NonCriticalException_UIContext await act.Should().NotThrowAsync(); CheckStoreIsCleared(); - // CheckUIContextIsCleared(cookie); + CheckUIContextIsCleared(cookie); logger.AssertPartialOutputStringExists("this is a test"); asyncLockReleaser.Received().Dispose(); } @@ -163,7 +163,7 @@ public async Task UpdateTaintVulnerabilitiesAsync_NoConfigurationScope_StoreAndU await testSubject.UpdateTaintVulnerabilitiesAsync(None); CheckStoreIsCleared(); - // CheckUIContextIsCleared(cookie); + CheckUIContextIsCleared(cookie); logger.AssertPartialOutputStringExists("not in connected mode"); slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); converter.ReceivedCalls().Should().BeEmpty(); @@ -179,7 +179,7 @@ public async Task UpdateTaintVulnerabilitiesAsync_StandaloneMode_StoreAndUIConte await testSubject.UpdateTaintVulnerabilitiesAsync(Standalone); CheckStoreIsCleared(); - // CheckUIContextIsCleared(cookie); + CheckUIContextIsCleared(cookie); logger.AssertPartialOutputStringExists("not in connected mode"); slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); toolWindowService.ReceivedCalls().Should().BeEmpty(); @@ -194,7 +194,7 @@ public async Task UpdateTaintVulnerabilitiesAsync_ConnectedModeConfigScope_NotRe await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedWithUninitializedRoot); CheckStoreIsCleared(); - // CheckUIContextIsCleared(cookie); + CheckUIContextIsCleared(cookie); slCoreServiceProvider.ReceivedCalls().Should().BeEmpty(); toolWindowService.ReceivedCalls().Should().BeEmpty(); } @@ -209,7 +209,7 @@ public async Task UpdateTaintVulnerabilitiesAsync_SLCoreNotInitialized_StoreAndU await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); CheckStoreIsCleared(); - // CheckUIContextIsCleared(cookie); + CheckUIContextIsCleared(cookie); Received.InOrder(() => { threadHandling.RunOnBackgroundThread(Arg.Any>>()); @@ -228,7 +228,6 @@ public async Task UpdateTaintVulnerabilitiesAsync_TaintStoreAlreadyInitialized_I await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); - // todo log taintStore.DidNotReceiveWithAnyArgs().Set(default, default); taintService.ReceivedCalls().Should().BeEmpty(); } @@ -242,7 +241,7 @@ public async Task UpdateTaintVulnerabilitiesAsync_NoIssuesForConfigScope_SetsSto await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); - // CheckUIContextIsCleared(cookie); + CheckUIContextIsCleared(cookie); converter.DidNotReceiveWithAnyArgs().Convert(default, default); taintStore.Received(1).Set(Arg.Is>(x => x.SequenceEqual(Array.Empty())), ConnectedReady.Id); toolWindowService.ReceivedCalls().Should().BeEmpty(); @@ -264,7 +263,7 @@ public async Task UpdateTaintVulnerabilitiesAsync_MultipleIssues_SetsStoreAndUIC await testSubject.UpdateTaintVulnerabilitiesAsync(ConnectedReady); - // CheckUIContextIsSet(cookie); + CheckUIContextIsSet(cookie); converter.ReceivedWithAnyArgs(3).Convert(default, default); taintStore.Received(1).Set(Arg.Is>(x => x.SequenceEqual(taintVisualizations)), ConnectedReady.Id); CheckToolWindowServiceIsCalled(); @@ -306,7 +305,7 @@ private static IVsUIServiceOperation CreateDefaultServiceOperation(IVsMonitorSel var serviceOp = Substitute.For(); // Set up the mock to invoke the operation with the supplied VS service - serviceOp.When(x => x.Execute(It.IsAny>())) + serviceOp.When(x => x.Execute(Arg.Any>())) .Do(call => call.Arg>().Invoke(svcToPassToCallback)); return serviceOp; From 5986a7ef114c04908b130f9e6eb6251cc670d35d Mon Sep 17 00:00:00 2001 From: Georgii Borovinskikh Date: Thu, 14 Nov 2024 11:13:35 +0100 Subject: [PATCH 9/9] CR issue fixes --- ...intIssuesConfigurationScopeMonitorTests.cs | 24 +++++++++---------- .../Taint/TaintIssuesSynchronizer.cs | 4 ++-- .../Taint/TaintResources.Designer.cs | 18 ++++++++++---- .../Taint/TaintResources.resx | 12 ++++++---- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs index 32d6c8c56f..04b0d5fe97 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesConfigurationScopeMonitorTests.cs @@ -26,22 +26,25 @@ namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint; [TestClass] public class TaintIssuesConfigurationScopeMonitorTests { - [TestMethod] - public void Ctor_SubscribesToConfigurationScopeEvents() - { - var activeConfigScopeTracker = Substitute.For(); + private TaintIssuesConfigurationScopeMonitor testSubject; + private IActiveConfigScopeTracker activeConfigScopeTracker; + private ITaintIssuesSynchronizer taintIssuesSynchronizer; - _ = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, Substitute.For()); + [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() { - var activeConfigScopeTracker = Substitute.For(); - var testSubject = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, Substitute.For()); - testSubject.Dispose(); activeConfigScopeTracker.Received().CurrentConfigurationScopeChanged -= Arg.Any(); @@ -50,11 +53,8 @@ public void Dispose_UnsubscribesToConfigurationScopeEvents() [TestMethod] public void ConfigScopeChangedEvent_CallsTaintSynchronizer() { - var activeConfigScopeTracker = Substitute.For(); var configurationScope = new ConfigurationScope("config scope"); activeConfigScopeTracker.Current.Returns(configurationScope); - var taintIssuesSynchronizer = Substitute.For(); - _ = new TaintIssuesConfigurationScopeMonitor(activeConfigScopeTracker, taintIssuesSynchronizer); activeConfigScopeTracker.CurrentConfigurationScopeChanged += Raise.Event(); diff --git a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs index 1ef878cd90..5dccbe2476 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -127,7 +127,7 @@ private bool IsConfigScopeReady(ConfigurationScope configurationScope) var isReady = configurationScope.RootPath is not null; if (!isReady) { - logger.LogVerbose("Configuration scope root hasn't been initialized."); + logger.LogVerbose(TaintResources.Synchronizer_Verbose_ConfigScopeNotReady); } return isReady; } @@ -137,7 +137,7 @@ private bool IsAlreadyInitializedForConfigScope(ConfigurationScope configuration var isAlreadyInitialized = taintStore.ConfigurationScope == configurationScope.Id; if (!isAlreadyInitialized) { - logger.LogVerbose("Taint store has already been initialized for current configuration scope"); + logger.LogVerbose(TaintResources.Synchronizer_Verbose_AlreadyInitialized); } return isAlreadyInitialized; } diff --git a/src/IssueViz.Security/Taint/TaintResources.Designer.cs b/src/IssueViz.Security/Taint/TaintResources.Designer.cs index fdabbf5bbf..f848b1871c 100644 --- a/src/IssueViz.Security/Taint/TaintResources.Designer.cs +++ b/src/IssueViz.Security/Taint/TaintResources.Designer.cs @@ -87,7 +87,7 @@ internal static string Synchronizer_NumberOfServerIssues { } /// - /// Looks up a localized string similar to [Taint] Unable to fetch taint vulnerabilities: SonarLint Core backend is not available.. + /// Looks up a localized string similar to [Taint] Unable to fetch taint vulnerabilities: SLCore backend is not available.. /// internal static string Synchronizer_SLCoreNotReady { get { @@ -96,12 +96,20 @@ internal static string Synchronizer_SLCoreNotReady { } /// - /// Looks up a localized string similar to [Taint] Displaying taint vulnerabilities in the IDE requires SonarQube v8.6 or later, or SonarCloud. Connected SonarQube version: v{0} - /// Visit {1} to find out more about this and other SonarLint features.. + /// Looks up a localized string similar to [Taint Sync] Taint storage has already been initialized for current configuration scope. /// - internal static string Synchronizer_UnsupportedSQVersion { + internal static string Synchronizer_Verbose_AlreadyInitialized { get { - return ResourceManager.GetString("Synchronizer_UnsupportedSQVersion", resourceCulture); + return ResourceManager.GetString("Synchronizer_Verbose_AlreadyInitialized", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [Taint Sync] Configuration scope root hasn't been initialized.... + /// + internal static string Synchronizer_Verbose_ConfigScopeNotReady { + get { + return ResourceManager.GetString("Synchronizer_Verbose_ConfigScopeNotReady", resourceCulture); } } diff --git a/src/IssueViz.Security/Taint/TaintResources.resx b/src/IssueViz.Security/Taint/TaintResources.resx index 49c82fb792..fcd41169fc 100644 --- a/src/IssueViz.Security/Taint/TaintResources.resx +++ b/src/IssueViz.Security/Taint/TaintResources.resx @@ -124,7 +124,7 @@ [Taint] Unable to fetch taint vulnerabilities: not in connected mode. - [Taint] Unable to fetch taint vulnerabilities: SonarLint Core backend is not available. + [Taint] Unable to fetch taint vulnerabilities: SLCore backend is not available. [Taint] Fetched {0} taint vulnerabilities. @@ -135,11 +135,13 @@ [Taint] Initializing taint issues synchronization package... - - [Taint] Displaying taint vulnerabilities in the IDE requires SonarQube v8.6 or later, or SonarCloud. Connected SonarQube version: v{0} - Visit {1} to find out more about this and other SonarLint features. - 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