diff --git a/src/IssueViz.Security.UnitTests/Taint/Models/TaintIssueTests.cs b/src/IssueViz.Security.UnitTests/Taint/Models/TaintIssueTests.cs index 26581da40..7b4fa7e22 100644 --- a/src/IssueViz.Security.UnitTests/Taint/Models/TaintIssueTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/Models/TaintIssueTests.cs @@ -18,74 +18,84 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; using SonarLint.VisualStudio.Core.Analysis; +using SonarLint.VisualStudio.IssueVisualization.Security.Taint; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; -namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint.Models +namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint.Models; + +[TestClass] +public class TaintIssueTests { - [TestClass] - public class TaintIssueTests + [TestMethod] + public void Ctor_NullLocation_ArgumentNullException() { - [TestMethod] - public void Ctor_NullLocation_ArgumentNullException() - { - Action act = () => new TaintIssue("issue key", "rule key", - null, - AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, DateTimeOffset.MinValue, DateTimeOffset.MinValue, null, null); + Action act = () => new TaintIssue("issue key", "rule key", + null, + AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, DateTimeOffset.MinValue, null, null); - act.Should().Throw().And.ParamName.Should().Be("primaryLocation"); - } + act.Should().Throw().And.ParamName.Should().Be("primaryLocation"); + } - [TestMethod] - public void Ctor_PropertiesSet() - { - var created = DateTimeOffset.Parse("2001-01-31T01:02:03+0200"); - var lastUpdated = DateTimeOffset.UtcNow; - var issue = new TaintIssue("issue key", "rule key", - new AnalysisIssueLocation("message", "local-path.cpp", new TextRange(1, 2, 3, 4, "hash")), - AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, created, lastUpdated, null, "contextKey"); + [TestMethod] + public void Ctor_PropertiesSet() + { + var created = DateTimeOffset.Parse("2001-01-31T01:02:03+0200"); + var issue = new TaintIssue("issue key", "rule key", + new AnalysisIssueLocation("message", "local-path.cpp", new TextRange(1, 2, 3, 4, "hash")), + AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, created, null, "contextKey"); - issue.IssueKey.Should().Be("issue key"); - issue.RuleKey.Should().Be("rule key"); - issue.Severity.Should().Be(AnalysisIssueSeverity.Major); - issue.CreationTimestamp.Should().Be(created); - issue.LastUpdateTimestamp.Should().Be(lastUpdated); - issue.RuleDescriptionContextKey.Should().Be("contextKey"); + issue.IssueKey.Should().Be("issue key"); + issue.RuleKey.Should().Be("rule key"); + issue.Severity.Should().Be(AnalysisIssueSeverity.Major); + issue.CreationTimestamp.Should().Be(created); + issue.RuleDescriptionContextKey.Should().Be("contextKey"); - issue.PrimaryLocation.FilePath.Should().Be("local-path.cpp"); - issue.PrimaryLocation.Message.Should().Be("message"); - issue.PrimaryLocation.TextRange.StartLine.Should().Be(1); - issue.PrimaryLocation.TextRange.EndLine.Should().Be(2); - issue.PrimaryLocation.TextRange.StartLineOffset.Should().Be(3); - issue.PrimaryLocation.TextRange.EndLineOffset.Should().Be(4); - issue.PrimaryLocation.TextRange.LineHash.Should().Be("hash"); - } + issue.PrimaryLocation.FilePath.Should().Be("local-path.cpp"); + issue.PrimaryLocation.Message.Should().Be("message"); + issue.PrimaryLocation.TextRange.StartLine.Should().Be(1); + issue.PrimaryLocation.TextRange.EndLine.Should().Be(2); + issue.PrimaryLocation.TextRange.StartLineOffset.Should().Be(3); + issue.PrimaryLocation.TextRange.EndLineOffset.Should().Be(4); + issue.PrimaryLocation.TextRange.LineHash.Should().Be("hash"); + } - [TestMethod] - public void Ctor_NoFlows_EmptyFlows() - { - IReadOnlyList flows = null; - var issue = new TaintIssue("issue key", "rule key", - new AnalysisIssueLocation("message", "local-path.cpp", new TextRange(1, 2, 3, 4, "hash")), - AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, DateTimeOffset.MinValue, DateTimeOffset.MaxValue, flows, null); + [TestMethod] + public void Ctor_NoFlows_EmptyFlows() + { + IReadOnlyList flows = null; + var issue = new TaintIssue("issue key", "rule key", + new AnalysisIssueLocation("message", "local-path.cpp", new TextRange(1, 2, 3, 4, "hash")), + AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, DateTimeOffset.MinValue, flows, null); - issue.Flows.Should().BeEmpty(); - } + issue.Flows.Should().BeEmpty(); + } - [TestMethod] - public void Ctor_HasFlows_CorrectFlows() - { - var flows = new[] { Mock.Of(), Mock.Of() }; - var issue = new TaintIssue("issue key", "rule key", - new AnalysisIssueLocation("message", "local-path.cpp", new TextRange(1, 2, 3, 4, "hash")), - AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, DateTimeOffset.MinValue, DateTimeOffset.MaxValue, flows, null); + [TestMethod] + public void Ctor_HasFlows_CorrectFlows() + { + var flows = new[] { Substitute.For(), Substitute.For() }; + var issue = new TaintIssue("issue key", "rule key", + new AnalysisIssueLocation("message", "local-path.cpp", new TextRange(1, 2, 3, 4, "hash")), + AnalysisIssueSeverity.Major, SoftwareQualitySeverity.High, DateTimeOffset.MinValue, flows, null); + + issue.Flows.Should().BeEquivalentTo(flows); + } + + [TestMethod] + public void Ctor_HasNoStandardAndNoCCTSeverity_Throws() + { + AnalysisIssueSeverity? analysisIssueSeverity = null; + SoftwareQualitySeverity? highestSoftwareQualitySeverity = null; + var act = () => new TaintIssue("issue key", + "rule key", + new AnalysisIssueLocation("msg", "local-path.cpp", new TextRange(1, 2, 3, 4, "hash")), + analysisIssueSeverity, + highestSoftwareQualitySeverity, + DateTimeOffset.Now, + [], + null); - issue.Flows.Should().BeEquivalentTo(flows); - } + act.Should().Throw().WithMessage(string.Format(TaintResources.TaintIssue_SeverityUndefined, "issue key")); } } diff --git a/src/IssueViz.Security.UnitTests/Taint/ServerSentEvents/TaintServerEventsListenerTests.cs b/src/IssueViz.Security.UnitTests/Taint/ServerSentEvents/TaintServerEventsListenerTests.cs index 55de241bf..ef6527091 100644 --- a/src/IssueViz.Security.UnitTests/Taint/ServerSentEvents/TaintServerEventsListenerTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/ServerSentEvents/TaintServerEventsListenerTests.cs @@ -1,310 +1,310 @@ -/* - * 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.Threading; -using System.Threading.Tasks; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Moq; -using SonarLint.VisualStudio.Core; -using SonarLint.VisualStudio.IssueVisualization.Models; -using SonarLint.VisualStudio.IssueVisualization.Security.Taint.ServerSentEvents; -using SonarLint.VisualStudio.IssueVisualization.Security.Taint; -using SonarLint.VisualStudio.TestInfrastructure; -using SonarQube.Client.Models.ServerSentEvents.ClientContract; - -namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint.ServerSentEvents -{ - [TestClass] - public class TaintServerEventsListenerTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public async Task OnEvent_UnrecognizedServerEvent_EventIsIgnored() - { - var taintStore = new Mock(); - var logger = new Mock(); - var taintServerEventSource = SetupTaintServerEventSource(Mock.Of()); - - var testSubject = CreateTestSubject( - taintServerEventSource: taintServerEventSource.Object, - taintStore: taintStore.Object, - logger: logger.Object); - - await testSubject.ListenAsync(); - - taintStore.Invocations.Count.Should().Be(0); - - logger.Verify(x => - x.LogVerbose(It.Is(s => s.Contains("ITestEvent"))), - Times.Once); - } - - [TestMethod] - public async Task OnEvent_TaintClosedServerEvent_IssueIsRemovedFromStore() - { - const string issueKey = "some issue1"; - - var serverEvent = CreateTaintClosedServerEvent(issueKey); - var taintServerEventSource = SetupTaintServerEventSource(serverEvent); - var taintStore = new Mock(); - - var testSubject = CreateTestSubject( - taintServerEventSource: taintServerEventSource.Object, - taintStore: taintStore.Object); - - await testSubject.ListenAsync(); - - taintStore.Verify(x => x.Remove(issueKey), Times.Once); - taintStore.VerifyNoOtherCalls(); - } - - [TestMethod] - public async Task OnEvent_TaintRaisedServerEvent_EventIsNotForCurrentBranch_IssueIgnored() - { - var taintIssue = Mock.Of(); - var serverEvent = CreateTaintRaisedServerEvent(taintIssue, "some branch"); - var taintServerEventSource = SetupTaintServerEventSource(serverEvent); - var serverBranchProvider = SetupBranchProvider("another branch"); - - var taintStore = new Mock(); - - var testSubject = CreateTestSubject( - taintServerEventSource: taintServerEventSource.Object, - taintStore: taintStore.Object, - serverBranchProvider: serverBranchProvider.Object); - - await testSubject.ListenAsync(); - - serverBranchProvider.Verify(x => x.GetServerBranchNameAsync(It.IsAny()), Times.Once); - taintStore.Invocations.Count.Should().Be(0); - } - - [TestMethod] - public async Task OnEvent_TaintRaisedServerEvent_EventIsForCurrentBranch_IssueIsAddedToStore() - { - const string branchName = "master"; - - var taintIssue = Mock.Of(); - var serverEvent = CreateTaintRaisedServerEvent(taintIssue, branchName); - var taintServerEventSource = SetupTaintServerEventSource(serverEvent); - var convertedIssueViz = Mock.Of(); - - var converter = new Mock(); - converter.Setup(x => x.Convert(taintIssue)) - .Returns(convertedIssueViz); - - var serverBranchProvider = SetupBranchProvider(branchName); - var taintStore = new Mock(); - - var testSubject = CreateTestSubject( - taintServerEventSource: taintServerEventSource.Object, - taintStore: taintStore.Object, - taintToIssueVizConverter: converter.Object, - serverBranchProvider: serverBranchProvider.Object); - - await testSubject.ListenAsync(); - - taintStore.Verify(x => x.Add(convertedIssueViz), Times.Once); - taintStore.VerifyNoOtherCalls(); - } - - [TestMethod] - public async Task OnEvent_FailureToProcessTaintEvent_NonCriticalException_EventIsIgnored() - { - var serverEvent1 = CreateTaintClosedServerEvent("some issue1"); - var serverEvent2 = CreateTaintClosedServerEvent("some issue2"); - var taintServerEventSource = SetupTaintServerEventSource(serverEvent1, serverEvent2); - var taintStore = new Mock(); - - taintStore - .Setup(x => x.Remove("some issue1")) - .Throws(new NotImplementedException("this is a test")); - - var logger = new Mock(); - - var testSubject = CreateTestSubject( - taintServerEventSource: taintServerEventSource.Object, - taintStore: taintStore.Object, - logger: logger.Object); - - await testSubject.ListenAsync(); - - taintStore.Verify(x => x.Remove("some issue1"), Times.Once); - taintStore.Verify(x => x.Remove("some issue2"), Times.Once); - taintStore.VerifyNoOtherCalls(); - logger.Verify(x=> x.LogVerbose(It.Is(s=> s.Contains("this is a test")), Array.Empty()), Times.Once()); - } - - [TestMethod] - public void OnEvent_FailureToProcessTaintEvent_CriticalException_StopsListeningToServerEventsSource() - { - var serverEvent1 = CreateTaintClosedServerEvent("some issue1"); - var serverEvent2 = CreateTaintClosedServerEvent("some issue2"); - var taintServerEventSource = SetupTaintServerEventSource(serverEvent1, serverEvent2); - var taintStore = new Mock(); - - taintStore - .Setup(x => x.Remove("some issue1")) - .Throws(new StackOverflowException("this is a test")); - - var testSubject = CreateTestSubject( - taintServerEventSource: taintServerEventSource.Object, - taintStore: taintStore.Object); - - Func func = async () => await testSubject.ListenAsync(); - - func.Should().Throw().And.Message.Should().Be("this is a test"); - - taintStore.Verify(x => x.Remove("some issue1"), Times.Once); - taintStore.Verify(x => x.Remove("some issue2"), Times.Never); - taintStore.VerifyNoOtherCalls(); - taintServerEventSource.Verify(x => x.GetNextEventOrNullAsync(), Times.Once); - taintServerEventSource.VerifyNoOtherCalls(); - } - - [TestMethod] - public async Task Dispose_StopsListeningToServerEventsSource() - { - var firstListenTask = new TaskCompletionSource(); - - var event1 = CreateTaintClosedServerEvent("issue1"); - var event2 = CreateTaintClosedServerEvent("issue2"); - - var taintServerEventSource = new Mock(); - taintServerEventSource - .SetupSequence(x => x.GetNextEventOrNullAsync()) - .Returns(firstListenTask.Task) - .ReturnsAsync(event2) - .ReturnsAsync((ITaintServerEvent) null); - - var taintStore = new Mock(); - - var testSubject = CreateTestSubject( - taintServerEventSource: taintServerEventSource.Object, - taintStore: taintStore.Object); - - var listenTask = testSubject.ListenAsync(); - - testSubject.Dispose(); - - firstListenTask.SetResult(event1); - - await listenTask; - - taintServerEventSource.Verify(x => x.GetNextEventOrNullAsync(), Times.Once); - taintStore.Verify(x=> x.Remove("issue1"), Times.Once); - taintStore.VerifyNoOtherCalls(); - } - - [TestMethod] - [Description("Regression test for https://github.com/SonarSource/sonarlint-visualstudio/issues/3946")] - public void Dispose_CalledASecondTime_NoException() - { - var testSubject = CreateTestSubject(); - - testSubject.Dispose(); - - Action act = () => testSubject.Dispose(); - act.Should().NotThrow(); - } - - private static ITaintVulnerabilityClosedServerEvent CreateTaintClosedServerEvent(string taintKey) - { - var serverEvent = new Mock(); - serverEvent.Setup(x => x.Key).Returns(taintKey); - - return serverEvent.Object; - } - - private static ITaintVulnerabilityRaisedServerEvent CreateTaintRaisedServerEvent(ITaintIssue taintIssue, string issueBranch) - { - var serverEvent = new Mock(); - serverEvent.Setup(x => x.Branch).Returns(issueBranch); - serverEvent.Setup(x => x.Issue).Returns(taintIssue); - - return serverEvent.Object; - } - - private static Mock SetupTaintServerEventSource(params ITaintServerEvent[] serverEvents) - { - var taintServerEventSource = new Mock(); - - var sequence = taintServerEventSource.SetupSequence(x => x.GetNextEventOrNullAsync()); - - foreach (var serverEvent in serverEvents) - { - sequence.ReturnsAsync(serverEvent); - } - - // Signal that the task is finished - sequence.ReturnsAsync((ITaintServerEvent)null); - - return taintServerEventSource; - } - - private static Mock SetupBranchProvider(string currentBranch) - { - var branchProvider = new Mock(); - branchProvider - .Setup(x => x.GetServerBranchNameAsync(It.IsAny())) - .ReturnsAsync(currentBranch); - - return branchProvider; - } - - private static TaintServerEventsListener CreateTestSubject( - IStatefulServerBranchProvider serverBranchProvider = null, - ITaintServerEventSource taintServerEventSource = null, - ITaintStore taintStore = null, - ITaintIssueToIssueVisualizationConverter taintToIssueVizConverter = null, - IThreadHandling threadHandling = null, - ILogger logger = null) - { - serverBranchProvider ??= Mock.Of(); - taintServerEventSource ??= Mock.Of(); - taintStore ??= Mock.Of(); - taintToIssueVizConverter ??= Mock.Of(); - threadHandling ??= new NoOpThreadHandler(); - logger ??= Mock.Of(); - - return new TaintServerEventsListener(serverBranchProvider, taintServerEventSource, taintStore, threadHandling, taintToIssueVizConverter, logger); - } - - /// - /// Used to test unrecognized taint events - /// - public interface ITestEvent : ITaintServerEvent - { - } - } -} +// /* +// * 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. +// */ +// // todo https://sonarsource.atlassian.net/browse/SLVS-1593 +// using System; +// using System.Threading; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Microsoft.VisualStudio.TestTools.UnitTesting; +// using Moq; +// using SonarLint.VisualStudio.Core; +// using SonarLint.VisualStudio.IssueVisualization.Models; +// using SonarLint.VisualStudio.IssueVisualization.Security.Taint.ServerSentEvents; +// using SonarLint.VisualStudio.IssueVisualization.Security.Taint; +// using SonarLint.VisualStudio.TestInfrastructure; +// using SonarQube.Client.Models.ServerSentEvents.ClientContract; +// +// namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint.ServerSentEvents +// { +// [TestClass] +// public class TaintServerEventsListenerTests +// { +// [TestMethod] +// public void MefCtor_CheckIsExported() +// { +// MefTestHelpers.CheckTypeCanBeImported( +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport(), +// MefTestHelpers.CreateExport()); +// } +// +// [TestMethod] +// public async Task OnEvent_UnrecognizedServerEvent_EventIsIgnored() +// { +// var taintStore = new Mock(); +// var logger = new Mock(); +// var taintServerEventSource = SetupTaintServerEventSource(Mock.Of()); +// +// var testSubject = CreateTestSubject( +// taintServerEventSource: taintServerEventSource.Object, +// taintStore: taintStore.Object, +// logger: logger.Object); +// +// await testSubject.ListenAsync(); +// +// taintStore.Invocations.Count.Should().Be(0); +// +// logger.Verify(x => +// x.LogVerbose(It.Is(s => s.Contains("ITestEvent"))), +// Times.Once); +// } +// +// [TestMethod] +// public async Task OnEvent_TaintClosedServerEvent_IssueIsRemovedFromStore() +// { +// const string issueKey = "some issue1"; +// +// var serverEvent = CreateTaintClosedServerEvent(issueKey); +// var taintServerEventSource = SetupTaintServerEventSource(serverEvent); +// var taintStore = new Mock(); +// +// var testSubject = CreateTestSubject( +// taintServerEventSource: taintServerEventSource.Object, +// taintStore: taintStore.Object); +// +// await testSubject.ListenAsync(); +// +// taintStore.Verify(x => x.Remove(issueKey), Times.Once); +// taintStore.VerifyNoOtherCalls(); +// } +// +// [TestMethod] +// public async Task OnEvent_TaintRaisedServerEvent_EventIsNotForCurrentBranch_IssueIgnored() +// { +// var taintIssue = Mock.Of(); +// var serverEvent = CreateTaintRaisedServerEvent(taintIssue, "some branch"); +// var taintServerEventSource = SetupTaintServerEventSource(serverEvent); +// var serverBranchProvider = SetupBranchProvider("another branch"); +// +// var taintStore = new Mock(); +// +// var testSubject = CreateTestSubject( +// taintServerEventSource: taintServerEventSource.Object, +// taintStore: taintStore.Object, +// serverBranchProvider: serverBranchProvider.Object); +// +// await testSubject.ListenAsync(); +// +// serverBranchProvider.Verify(x => x.GetServerBranchNameAsync(It.IsAny()), Times.Once); +// taintStore.Invocations.Count.Should().Be(0); +// } +// +// [TestMethod] +// public async Task OnEvent_TaintRaisedServerEvent_EventIsForCurrentBranch_IssueIsAddedToStore() +// { +// const string branchName = "master"; +// +// var taintIssue = Mock.Of(); +// var serverEvent = CreateTaintRaisedServerEvent(taintIssue, branchName); +// var taintServerEventSource = SetupTaintServerEventSource(serverEvent); +// var convertedIssueViz = Mock.Of(); +// +// var converter = new Mock(); +// converter.Setup(x => x.Convert(taintIssue)) +// .Returns(convertedIssueViz); +// +// var serverBranchProvider = SetupBranchProvider(branchName); +// var taintStore = new Mock(); +// +// var testSubject = CreateTestSubject( +// taintServerEventSource: taintServerEventSource.Object, +// taintStore: taintStore.Object, +// taintToIssueVizConverter: converter.Object, +// serverBranchProvider: serverBranchProvider.Object); +// +// await testSubject.ListenAsync(); +// +// taintStore.Verify(x => x.Add(convertedIssueViz), Times.Once); +// taintStore.VerifyNoOtherCalls(); +// } +// +// [TestMethod] +// public async Task OnEvent_FailureToProcessTaintEvent_NonCriticalException_EventIsIgnored() +// { +// var serverEvent1 = CreateTaintClosedServerEvent("some issue1"); +// var serverEvent2 = CreateTaintClosedServerEvent("some issue2"); +// var taintServerEventSource = SetupTaintServerEventSource(serverEvent1, serverEvent2); +// var taintStore = new Mock(); +// +// taintStore +// .Setup(x => x.Remove("some issue1")) +// .Throws(new NotImplementedException("this is a test")); +// +// var logger = new Mock(); +// +// var testSubject = CreateTestSubject( +// taintServerEventSource: taintServerEventSource.Object, +// taintStore: taintStore.Object, +// logger: logger.Object); +// +// await testSubject.ListenAsync(); +// +// taintStore.Verify(x => x.Remove("some issue1"), Times.Once); +// taintStore.Verify(x => x.Remove("some issue2"), Times.Once); +// taintStore.VerifyNoOtherCalls(); +// logger.Verify(x=> x.LogVerbose(It.Is(s=> s.Contains("this is a test")), Array.Empty()), Times.Once()); +// } +// +// [TestMethod] +// public void OnEvent_FailureToProcessTaintEvent_CriticalException_StopsListeningToServerEventsSource() +// { +// var serverEvent1 = CreateTaintClosedServerEvent("some issue1"); +// var serverEvent2 = CreateTaintClosedServerEvent("some issue2"); +// var taintServerEventSource = SetupTaintServerEventSource(serverEvent1, serverEvent2); +// var taintStore = new Mock(); +// +// taintStore +// .Setup(x => x.Remove("some issue1")) +// .Throws(new StackOverflowException("this is a test")); +// +// var testSubject = CreateTestSubject( +// taintServerEventSource: taintServerEventSource.Object, +// taintStore: taintStore.Object); +// +// Func func = async () => await testSubject.ListenAsync(); +// +// func.Should().Throw().And.Message.Should().Be("this is a test"); +// +// taintStore.Verify(x => x.Remove("some issue1"), Times.Once); +// taintStore.Verify(x => x.Remove("some issue2"), Times.Never); +// taintStore.VerifyNoOtherCalls(); +// taintServerEventSource.Verify(x => x.GetNextEventOrNullAsync(), Times.Once); +// taintServerEventSource.VerifyNoOtherCalls(); +// } +// +// [TestMethod] +// public async Task Dispose_StopsListeningToServerEventsSource() +// { +// var firstListenTask = new TaskCompletionSource(); +// +// var event1 = CreateTaintClosedServerEvent("issue1"); +// var event2 = CreateTaintClosedServerEvent("issue2"); +// +// var taintServerEventSource = new Mock(); +// taintServerEventSource +// .SetupSequence(x => x.GetNextEventOrNullAsync()) +// .Returns(firstListenTask.Task) +// .ReturnsAsync(event2) +// .ReturnsAsync((ITaintServerEvent) null); +// +// var taintStore = new Mock(); +// +// var testSubject = CreateTestSubject( +// taintServerEventSource: taintServerEventSource.Object, +// taintStore: taintStore.Object); +// +// var listenTask = testSubject.ListenAsync(); +// +// testSubject.Dispose(); +// +// firstListenTask.SetResult(event1); +// +// await listenTask; +// +// taintServerEventSource.Verify(x => x.GetNextEventOrNullAsync(), Times.Once); +// taintStore.Verify(x=> x.Remove("issue1"), Times.Once); +// taintStore.VerifyNoOtherCalls(); +// } +// +// [TestMethod] +// [Description("Regression test for https://github.com/SonarSource/sonarlint-visualstudio/issues/3946")] +// public void Dispose_CalledASecondTime_NoException() +// { +// var testSubject = CreateTestSubject(); +// +// testSubject.Dispose(); +// +// Action act = () => testSubject.Dispose(); +// act.Should().NotThrow(); +// } +// +// private static ITaintVulnerabilityClosedServerEvent CreateTaintClosedServerEvent(string taintKey) +// { +// var serverEvent = new Mock(); +// serverEvent.Setup(x => x.Key).Returns(taintKey); +// +// return serverEvent.Object; +// } +// +// private static ITaintVulnerabilityRaisedServerEvent CreateTaintRaisedServerEvent(ITaintIssue taintIssue, string issueBranch) +// { +// var serverEvent = new Mock(); +// serverEvent.Setup(x => x.Branch).Returns(issueBranch); +// serverEvent.Setup(x => x.Issue).Returns(taintIssue); +// +// return serverEvent.Object; +// } +// +// private static Mock SetupTaintServerEventSource(params ITaintServerEvent[] serverEvents) +// { +// var taintServerEventSource = new Mock(); +// +// var sequence = taintServerEventSource.SetupSequence(x => x.GetNextEventOrNullAsync()); +// +// foreach (var serverEvent in serverEvents) +// { +// sequence.ReturnsAsync(serverEvent); +// } +// +// // Signal that the task is finished +// sequence.ReturnsAsync((ITaintServerEvent)null); +// +// return taintServerEventSource; +// } +// +// private static Mock SetupBranchProvider(string currentBranch) +// { +// var branchProvider = new Mock(); +// branchProvider +// .Setup(x => x.GetServerBranchNameAsync(It.IsAny())) +// .ReturnsAsync(currentBranch); +// +// return branchProvider; +// } +// +// private static TaintServerEventsListener CreateTestSubject( +// IStatefulServerBranchProvider serverBranchProvider = null, +// ITaintServerEventSource taintServerEventSource = null, +// ITaintStore taintStore = null, +// ITaintIssueToIssueVisualizationConverter taintToIssueVizConverter = null, +// IThreadHandling threadHandling = null, +// ILogger logger = null) +// { +// serverBranchProvider ??= Mock.Of(); +// taintServerEventSource ??= Mock.Of(); +// taintStore ??= Mock.Of(); +// taintToIssueVizConverter ??= Mock.Of(); +// threadHandling ??= new NoOpThreadHandler(); +// logger ??= Mock.Of(); +// +// return new TaintServerEventsListener(serverBranchProvider, taintServerEventSource, taintStore, threadHandling, taintToIssueVizConverter, logger); +// } +// +// /// +// /// Used to test unrecognized taint events +// /// +// public interface ITestEvent : ITaintServerEvent +// { +// } +// } +// } diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssueToIssueVisualizationConverterTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssueToIssueVisualizationConverterTests.cs index de484bad3..b61cc23e1 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssueToIssueVisualizationConverterTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssueToIssueVisualizationConverterTests.cs @@ -18,523 +18,257 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Linq; -using FluentAssertions; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using Microsoft.VisualStudio.Text; -using Moq; -using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Taint; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; +using SonarLint.VisualStudio.SLCore.Common.Models; +using SonarLint.VisualStudio.SLCore.Protocol; +using SonarLint.VisualStudio.SLCore.Service.Rules.Models; using SonarLint.VisualStudio.TestInfrastructure; -using SonarQube.Client.Models; -using SonarQube.Client.Models.ServerSentEvents.ClientContract; -using ITaintIssue = SonarQube.Client.Models.ServerSentEvents.ClientContract.ITaintIssue; -using ITextRange = SonarQube.Client.Models.ServerSentEvents.ClientContract.ITextRange; +using SoftwareQuality = SonarLint.VisualStudio.SLCore.Common.Models.SoftwareQuality; -namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint -{ - [TestClass] - public class TaintIssueToIssueVisualizationConverterTests - { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void Convert_FromSonarQubeIssue_ServerIssueHasNoTextRange_ArgumentNullException() - { - var serverIssue = CreateServerIssue(textRange: null); - - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Convert(serverIssue); - act.Should().Throw().And.ParamName.Should().Be("TextRange"); - } - - [TestMethod] - public void Convert_FromSonarQubeIssue_FlowLocationHasNoTextRange_ArgumentNullException() - { - var serverLocation = CreateServerLocation(textRange: null); - var serverFlow = CreateServerFlow(serverLocation); - var sonarQubeIssue = CreateServerIssue(textRange: new IssueTextRange(1, 1, 1, 1), flows: serverFlow); - - var testSubject = CreateTestSubject(); - - Action act = () => testSubject.Convert(sonarQubeIssue); - act.Should().Throw().And.ParamName.Should().Be("TextRange"); - } - - [TestMethod] - public void Convert_FromSonarQubeIssue_IssueVizConverterCalledWithCorrectParameters_ReturnsConvertedIssueVizWithReversedLocations() - { - var location1 = CreateServerLocation("path1", "message1", new IssueTextRange(1, 2, 3, 4)); - var location2 = CreateServerLocation("path2", "message2", new IssueTextRange(5, 6, 7, 8)); - var flow1 = CreateServerFlow(location1, location2); - - var location3 = CreateServerLocation("path3", "message3", new IssueTextRange(9, 10, 11, 12)); - var flow2 = CreateServerFlow(location3); - - var created = DateTimeOffset.Parse("2001-12-30T01:02:03+0000"); - var lastUpdate = DateTimeOffset.Parse("2009-02-01T13:14:15+0200"); - - var issue = CreateServerIssue("issue key", - "path4", - "hash", - "message4", - "rule", - SonarQubeIssueSeverity.Major, - cctSeverities: new Dictionary - { - { SonarQubeSoftwareQuality.Security, SonarQubeSoftwareQualitySeverity.Medium } - }, - new IssueTextRange(13, 14, 15, 16), - created, - lastUpdate, - resolved: true, - contextKey: "contextKey", - flow1, - flow2); - - var expectedConvertedIssueViz = CreateIssueViz(); - var issueVizConverter = new Mock(); - issueVizConverter - .Setup(x => x.Convert(It.IsAny(), null)) - .Returns(expectedConvertedIssueViz); - - var testSubject = CreateTestSubject(issueVizConverter.Object); - var result = testSubject.Convert(issue); - - result.Should().BeSameAs(expectedConvertedIssueViz); - - issueVizConverter.Verify(x => x.Convert( - It.Is((TaintIssue taintIssue) => - taintIssue.IssueKey == "issue key" && - taintIssue.RuleKey == "rule" && - taintIssue.Severity == AnalysisIssueSeverity.Major && - taintIssue.HighestSoftwareQualitySeverity == SoftwareQualitySeverity.Medium && - taintIssue.RuleDescriptionContextKey == "contextKey" && - - taintIssue.PrimaryLocation.FilePath == "path4" && - taintIssue.PrimaryLocation.Message == "message4" && - taintIssue.PrimaryLocation.TextRange.LineHash == "hash" && - taintIssue.PrimaryLocation.TextRange.StartLine == 13 && - taintIssue.PrimaryLocation.TextRange.EndLine == 14 && - taintIssue.PrimaryLocation.TextRange.StartLineOffset == 15 && - taintIssue.PrimaryLocation.TextRange.EndLineOffset == 16 && - - taintIssue.CreationTimestamp == created && - taintIssue.LastUpdateTimestamp == lastUpdate && - - taintIssue.Flows.Count == 2 && - taintIssue.Flows[0].Locations.Count == 2 && - taintIssue.Flows[1].Locations.Count == 1 && - - taintIssue.Flows[0].Locations[0].Message == "message2" && - taintIssue.Flows[0].Locations[0].FilePath == "path2" && - taintIssue.Flows[0].Locations[0].TextRange.LineHash == null && - taintIssue.Flows[0].Locations[0].TextRange.StartLine == 5 && - taintIssue.Flows[0].Locations[0].TextRange.EndLine == 6 && - taintIssue.Flows[0].Locations[0].TextRange.StartLineOffset == 7 && - taintIssue.Flows[0].Locations[0].TextRange.EndLineOffset == 8 && - - taintIssue.Flows[0].Locations[1].Message == "message1" && - taintIssue.Flows[0].Locations[1].FilePath == "path1" && - taintIssue.Flows[0].Locations[1].TextRange.LineHash == null && - taintIssue.Flows[0].Locations[1].TextRange.StartLine == 1 && - taintIssue.Flows[0].Locations[1].TextRange.EndLine == 2 && - taintIssue.Flows[0].Locations[1].TextRange.StartLineOffset == 3 && - taintIssue.Flows[0].Locations[1].TextRange.EndLineOffset == 4 && - - taintIssue.Flows[1].Locations[0].Message == "message3" && - taintIssue.Flows[1].Locations[0].FilePath == "path3" && - taintIssue.Flows[1].Locations[0].TextRange.LineHash == null && - taintIssue.Flows[1].Locations[0].TextRange.StartLine == 9 && - taintIssue.Flows[1].Locations[0].TextRange.EndLine == 10 && - taintIssue.Flows[1].Locations[0].TextRange.StartLineOffset == 11 && - taintIssue.Flows[1].Locations[0].TextRange.EndLineOffset == 12 - ), - It.IsAny()), - Times.Once); - } - - [TestMethod] - [DataRow(SonarQubeIssueSeverity.Blocker, AnalysisIssueSeverity.Blocker)] - [DataRow(SonarQubeIssueSeverity.Critical, AnalysisIssueSeverity.Critical)] - [DataRow(SonarQubeIssueSeverity.Info, AnalysisIssueSeverity.Info)] - [DataRow(SonarQubeIssueSeverity.Major, AnalysisIssueSeverity.Major)] - [DataRow(SonarQubeIssueSeverity.Minor, AnalysisIssueSeverity.Minor)] - public void Convert_KnownSeverity_ConvertedToAnalysisIssueSeverity(SonarQubeIssueSeverity sqSeverity, AnalysisIssueSeverity expectedSeverity) - { - var result = TaintIssueToIssueVisualizationConverter.Convert(sqSeverity); - - result.Should().Be(expectedSeverity); - } - - [TestMethod] - public void ConvertToHighestSeverity_NullSeverities_ReturnsNull() - { - var result = TaintIssueToIssueVisualizationConverter.ConvertToHighestSeverity(null); - - result.Should().Be(null); - } - - [TestMethod] - public void ConvertToHighestSeverity_EmptySeverities_ReturnsNull() - { - var result = TaintIssueToIssueVisualizationConverter.ConvertToHighestSeverity(new Dictionary()); - - result.Should().Be(null); - } - - [DataTestMethod] - [DataRow(SonarQubeSoftwareQualitySeverity.High, SoftwareQualitySeverity.High)] - [DataRow(SonarQubeSoftwareQualitySeverity.Medium, SoftwareQualitySeverity.Medium)] - [DataRow(SonarQubeSoftwareQualitySeverity.Low, SoftwareQualitySeverity.Low)] - public void ConvertToHighestSeverity_SingleSeverity_ReturnsIt(SonarQubeSoftwareQualitySeverity sqSeverity, - SoftwareQualitySeverity expectedSeverity) - { - var impacts = new Dictionary - { - { SonarQubeSoftwareQuality.Maintainability, sqSeverity } - }; - - var result = TaintIssueToIssueVisualizationConverter.ConvertToHighestSeverity(impacts); - - result.Should().Be(expectedSeverity); - } - - [TestMethod] - public void ConvertToHighestSeverity_InvalidSeverity_Throws() - { - var impacts = new Dictionary - { - { SonarQubeSoftwareQuality.Maintainability, (SonarQubeSoftwareQualitySeverity)999 } - }; - - var act = () => TaintIssueToIssueVisualizationConverter.ConvertToHighestSeverity(impacts); - - act.Should().Throw(); - } - - [DataTestMethod] - [DataRow(new []{SonarQubeSoftwareQualitySeverity.Low, SonarQubeSoftwareQualitySeverity.Low, SonarQubeSoftwareQualitySeverity.Low}, SoftwareQualitySeverity.Low)] - [DataRow(new []{SonarQubeSoftwareQualitySeverity.Low, SonarQubeSoftwareQualitySeverity.Medium, SonarQubeSoftwareQualitySeverity.Low}, SoftwareQualitySeverity.Medium)] - [DataRow(new []{SonarQubeSoftwareQualitySeverity.High, SonarQubeSoftwareQualitySeverity.Medium, SonarQubeSoftwareQualitySeverity.Low}, SoftwareQualitySeverity.High)] - public void ConvertToHighestSeverity_MultipleSeverities_ReturnsHighest(SonarQubeSoftwareQualitySeverity[] sqSeverities, SoftwareQualitySeverity expectedSeverity) - { - if (sqSeverities.Length != 3) - { - Assert.Fail("Wrong length of the list"); - } - - var impacts = new Dictionary - { - { SonarQubeSoftwareQuality.Maintainability, sqSeverities[0] }, - { SonarQubeSoftwareQuality.Reliability, sqSeverities[1] }, - { SonarQubeSoftwareQuality.Security, sqSeverities[2] }, - }; - - var result = TaintIssueToIssueVisualizationConverter.ConvertToHighestSeverity(impacts); - - result.Should().Be(expectedSeverity); - } - - [TestMethod] - [DataRow(SonarQubeIssueSeverity.Unknown)] - [DataRow((SonarQubeIssueSeverity)1234)] - public void Convert_UnknownSeverity_ArgumentOutOfRangeException(SonarQubeIssueSeverity sqSeverity) - { - Action act = () => TaintIssueToIssueVisualizationConverter.Convert(sqSeverity); - - act.Should().Throw().And.ParamName.Should().Be("issueSeverity"); - } - - public enum OriginalIssueType - { - SonarQubeIssue, - TaintSonarQubeIssue - } - - [TestMethod] - [DataRow(OriginalIssueType.SonarQubeIssue)] - [DataRow(OriginalIssueType.TaintSonarQubeIssue)] - public void Convert_CalculatesLocalFilePaths(OriginalIssueType originalIssueType) - { - var locationViz1 = CreateLocationViz("server-path1"); - var locationViz2 = CreateLocationViz("server-path2"); - var locationViz3 = CreateLocationViz("server-path3"); - var expectedIssueViz = CreateIssueViz("server-path4", locationViz1, locationViz2, locationViz3); - - var issueVizConverter = new Mock(); - issueVizConverter - .Setup(x => x.Convert(It.IsAny(), null)) - .Returns(expectedIssueViz); - - var absoluteFilePathLocator = new Mock(); - absoluteFilePathLocator.Setup(x => x.Locate("server-path1")).Returns("local1"); - absoluteFilePathLocator.Setup(x => x.Locate("server-path2")).Returns((string)null); - absoluteFilePathLocator.Setup(x => x.Locate("server-path3")).Returns("local3"); - absoluteFilePathLocator.Setup(x => x.Locate("server-path4")).Returns("local4"); - - var testSubject = CreateTestSubject(issueVizConverter.Object, absoluteFilePathLocator.Object); +namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.Taint; - var result = originalIssueType == OriginalIssueType.SonarQubeIssue - ? testSubject.Convert(CreateDummySonarQubeIssue()) - : testSubject.Convert(CreateDummyTaintSonarQubeIssue()); - - result.Should().Be(expectedIssueViz); - - expectedIssueViz.CurrentFilePath.Should().Be("local4"); - - var secondaryLocations = expectedIssueViz.GetSecondaryLocations().ToList(); - secondaryLocations[0].CurrentFilePath.Should().Be("local1"); - secondaryLocations[1].CurrentFilePath.Should().Be(null); - secondaryLocations[2].CurrentFilePath.Should().Be("local3"); - } - - [TestMethod] - public void Convert_FromTaintIssue_IssueVizConverterCalledWithCorrectParameters_ReturnsConvertedIssueVizWithReversedLocations() - { - var location1 = CreateTaintServerLocation("path1", "message1", CreateTaintTextRange(1, 2, 3, 4, "hash1")); - var location2 = CreateTaintServerLocation("path2", "message2", CreateTaintTextRange(5, 6, 7, 8, "hash2")); - var flow1 = CreateTaintServerFlow(location1, location2); - - var location3 = CreateTaintServerLocation("path3", "message3", CreateTaintTextRange(9, 10, 11, 12, "hash3")); - var flow2 = CreateTaintServerFlow(location3); - - var mainLocation = CreateTaintServerLocation("path4", "message4", CreateTaintTextRange(13, 14, 15, 16, "hash4")); - var creationDate = DateTimeOffset.UtcNow; - var issue = CreateTaintServerIssue("issue key", - "rule", - creationDate, - SonarQubeIssueSeverity.Major, - new Dictionary - { - { SonarQubeSoftwareQuality.Security, SonarQubeSoftwareQualitySeverity.Low}, - { SonarQubeSoftwareQuality.Maintainability, SonarQubeSoftwareQualitySeverity.Medium}, - }, - mainLocation, - flow1, - flow2); - - var expectedConvertedIssueViz = CreateIssueViz(); - var issueVizConverter = new Mock(); - issueVizConverter - .Setup(x => x.Convert(It.IsAny(), null)) - .Returns(expectedConvertedIssueViz); - - var testSubject = CreateTestSubject(issueVizConverter.Object); - var result = testSubject.Convert(issue); - - result.Should().BeSameAs(expectedConvertedIssueViz); - - issueVizConverter.Verify(x => x.Convert( - It.Is((TaintIssue taintIssue) => - taintIssue.IssueKey == "issue key" && - taintIssue.RuleKey == "rule" && - taintIssue.Severity == AnalysisIssueSeverity.Major && - taintIssue.HighestSoftwareQualitySeverity == SoftwareQualitySeverity.Medium && - - taintIssue.PrimaryLocation.FilePath == "path4" && - taintIssue.PrimaryLocation.Message == "message4" && - taintIssue.PrimaryLocation.TextRange.LineHash == "hash4" && - taintIssue.PrimaryLocation.TextRange.StartLine == 13 && - taintIssue.PrimaryLocation.TextRange.EndLine == 14 && - taintIssue.PrimaryLocation.TextRange.StartLineOffset == 15 && - taintIssue.PrimaryLocation.TextRange.EndLineOffset == 16 && - - taintIssue.CreationTimestamp == creationDate && - taintIssue.LastUpdateTimestamp == default && - - taintIssue.Flows.Count == 2 && - taintIssue.Flows[0].Locations.Count == 2 && - taintIssue.Flows[1].Locations.Count == 1 && - - taintIssue.Flows[0].Locations[0].Message == "message2" && - taintIssue.Flows[0].Locations[0].FilePath == "path2" && - taintIssue.Flows[0].Locations[0].TextRange.LineHash == "hash2" && - taintIssue.Flows[0].Locations[0].TextRange.StartLine == 5 && - taintIssue.Flows[0].Locations[0].TextRange.EndLine == 6 && - taintIssue.Flows[0].Locations[0].TextRange.StartLineOffset == 7 && - taintIssue.Flows[0].Locations[0].TextRange.EndLineOffset == 8 && - - taintIssue.Flows[0].Locations[1].Message == "message1" && - taintIssue.Flows[0].Locations[1].FilePath == "path1" && - taintIssue.Flows[0].Locations[1].TextRange.LineHash == "hash1" && - taintIssue.Flows[0].Locations[1].TextRange.StartLine == 1 && - taintIssue.Flows[0].Locations[1].TextRange.EndLine == 2 && - taintIssue.Flows[0].Locations[1].TextRange.StartLineOffset == 3 && - taintIssue.Flows[0].Locations[1].TextRange.EndLineOffset == 4 && - - taintIssue.Flows[1].Locations[0].Message == "message3" && - taintIssue.Flows[1].Locations[0].FilePath == "path3" && - taintIssue.Flows[1].Locations[0].TextRange.LineHash == "hash3" && - taintIssue.Flows[1].Locations[0].TextRange.StartLine == 9 && - taintIssue.Flows[1].Locations[0].TextRange.EndLine == 10 && - taintIssue.Flows[1].Locations[0].TextRange.StartLineOffset == 11 && - taintIssue.Flows[1].Locations[0].TextRange.EndLineOffset == 12 - ), - It.IsAny()), - Times.Once); - } - - [TestMethod] - [DataRow(true)] - [DataRow(false)] - public void Convert_FromSonarQubeIssue_IssueVizIsCorrectlyMarkedAsSuppressed(bool isIssueSuppressed) - { - var issue = CreateServerIssue(resolved: isIssueSuppressed, textRange: new IssueTextRange(1, 2, 3, 4)); +[TestClass] +public class TaintIssueToIssueVisualizationConverterTests +{ + private IAnalysisIssueVisualizationConverter issueVizConverter; + private TaintIssueToIssueVisualizationConverter testSubject; - var expectedConvertedIssueViz = CreateIssueViz(); + [TestInitialize] + public void TestInitialize() + { + issueVizConverter = Substitute.For(); + testSubject = new TaintIssueToIssueVisualizationConverter(issueVizConverter); + } - var issueVizConverter = new Mock(); - issueVizConverter - .Setup(x => x.Convert(It.IsAny(), null)) - .Returns(expectedConvertedIssueViz); + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport()); - var testSubject = CreateTestSubject(issueVizConverter.Object); - var result = testSubject.Convert(issue); + [TestMethod] + public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent(); - result.Should().Be(expectedConvertedIssueViz); - result.IsSuppressed.Should().Be(isIssueSuppressed); - } - - [TestMethod] - public void Convert_FromTaintIssue_IssueVizIsNotSuppressed() - { - var taintIssue = CreateTaintServerIssue( - mainLocation: CreateTaintServerLocation( - textRange: CreateTaintTextRange(1, 2, 3, 4, null))); + [TestMethod] + public void Convert_IssueVizConverterCalledWithCorrectParameters_ReturnsConvertedIssueVizWithReversedLocations() + { + var created = DateTimeOffset.Parse("2001-12-30T01:02:03+0000"); + var taintDto = new TaintVulnerabilityDto( + Guid.Parse("efa697a2-9cfd-4faf-ba21-71b378667a81"), + "serverkey", + true, + "rulekey:S123", + "message1", + "file\\path\\1", + created, + new StandardModeDetails(IssueSeverity.MINOR, RuleType.VULNERABILITY), + [ + new TaintFlowDto( + [ + new TaintFlowLocationDto( + new TextRangeWithHashDto(5, 6, 7, 8, "hash2"), + "message2", + "file\\path\\2"), + new TaintFlowLocationDto( + new TextRangeWithHashDto(9, 10, 11, 12, "hash3"), + "message3", + "file\\path\\3") + ]), + new TaintFlowDto( + [ + new TaintFlowLocationDto( + new TextRangeWithHashDto(13, 14, 15, 16, "hash4"), + "message4", + "file\\path\\4") + ]) + ], + new TextRangeWithHashDto(1, 2, 3, 4, "hash1"), + "rulecontext", + false); + + var expectedConvertedIssueViz = CreateIssueViz(); + issueVizConverter.Convert(Arg.Any()) + .Returns(expectedConvertedIssueViz); + + var result = testSubject.Convert(taintDto, "C:\\root"); + + result.Should().BeSameAs(expectedConvertedIssueViz); + issueVizConverter.Received().Convert( + Arg.Is((TaintIssue taintIssue) => + taintIssue.IssueKey == "serverkey" && + taintIssue.RuleKey == "rulekey:S123" && + taintIssue.Severity == AnalysisIssueSeverity.Minor && + taintIssue.HighestSoftwareQualitySeverity == null && + taintIssue.RuleDescriptionContextKey == "rulecontext" && + taintIssue.PrimaryLocation.FilePath == @"C:\root\file\path\1" && + taintIssue.PrimaryLocation.Message == "message1" && + taintIssue.PrimaryLocation.TextRange.LineHash == "hash1" && + taintIssue.PrimaryLocation.TextRange.StartLine == 1 && + taintIssue.PrimaryLocation.TextRange.EndLine == 3 && + taintIssue.PrimaryLocation.TextRange.StartLineOffset == 2 && + taintIssue.PrimaryLocation.TextRange.EndLineOffset == 4 && + taintIssue.CreationTimestamp == created && + taintIssue.Flows.Count == 2 && + taintIssue.Flows[0].Locations.Count == 2 && + taintIssue.Flows[1].Locations.Count == 1 && + taintIssue.Flows[0].Locations[0].Message == "message2" && + taintIssue.Flows[0].Locations[0].FilePath == @"C:\root\file\path\2" && + taintIssue.Flows[0].Locations[0].TextRange.LineHash == "hash2" && + taintIssue.Flows[0].Locations[0].TextRange.StartLine == 5 && + taintIssue.Flows[0].Locations[0].TextRange.EndLine == 7 && + taintIssue.Flows[0].Locations[0].TextRange.StartLineOffset == 6 && + taintIssue.Flows[0].Locations[0].TextRange.EndLineOffset == 8 && + taintIssue.Flows[0].Locations[1].Message == "message3" && + taintIssue.Flows[0].Locations[1].FilePath == @"C:\root\file\path\3" && + taintIssue.Flows[0].Locations[1].TextRange.LineHash == "hash3" && + taintIssue.Flows[0].Locations[1].TextRange.StartLine == 9 && + taintIssue.Flows[0].Locations[1].TextRange.EndLine == 11 && + taintIssue.Flows[0].Locations[1].TextRange.StartLineOffset == 10 && + taintIssue.Flows[0].Locations[1].TextRange.EndLineOffset == 12 && + taintIssue.Flows[1].Locations[0].Message == "message4" && + taintIssue.Flows[1].Locations[0].FilePath == @"C:\root\file\path\4" && + taintIssue.Flows[1].Locations[0].TextRange.LineHash == "hash4" && + taintIssue.Flows[1].Locations[0].TextRange.StartLine == 13 && + taintIssue.Flows[1].Locations[0].TextRange.EndLine == 15 && + taintIssue.Flows[1].Locations[0].TextRange.StartLineOffset == 14 && + taintIssue.Flows[1].Locations[0].TextRange.EndLineOffset == 16 + )); + } - var expectedConvertedIssueViz = CreateIssueViz(); + [DataTestMethod] + [DataRow(IssueSeverity.INFO, AnalysisIssueSeverity.Info)] + [DataRow(IssueSeverity.MINOR, AnalysisIssueSeverity.Minor)] + [DataRow(IssueSeverity.CRITICAL, AnalysisIssueSeverity.Critical)] + [DataRow(IssueSeverity.MAJOR, AnalysisIssueSeverity.Major)] + [DataRow(IssueSeverity.BLOCKER, AnalysisIssueSeverity.Blocker)] + public void StandardSeverity_Converts(IssueSeverity slCoreSeverity, AnalysisIssueSeverity expectedSeverity) + { + var taintVulnerabilityDto = CreateDefaultTaintDto(new StandardModeDetails(slCoreSeverity, default)); - var issueVizConverter = new Mock(); - issueVizConverter - .Setup(x => x.Convert(It.IsAny(), null)) - .Returns(expectedConvertedIssueViz); + testSubject.Convert(taintVulnerabilityDto, "C:\\root"); - var testSubject = CreateTestSubject(issueVizConverter.Object); - var result = testSubject.Convert(taintIssue); + issueVizConverter.Received() + .Convert(Arg.Is(x => x.HighestSoftwareQualitySeverity == null && x.Severity == expectedSeverity)); + } - result.Should().Be(expectedConvertedIssueViz); - result.IsSuppressed.Should().BeFalse(); - } + [TestMethod] + public void StandardSeverity_InvalidSeverity_Throws() + { + var taintVulnerabilityDto = CreateDefaultTaintDto(new StandardModeDetails((IssueSeverity)999, default)); - private static TaintIssueToIssueVisualizationConverter CreateTestSubject(IAnalysisIssueVisualizationConverter issueVizConverter = null, IAbsoluteFilePathLocator absoluteFilePathLocator = null) - { - issueVizConverter ??= Mock.Of(); - absoluteFilePathLocator ??= Mock.Of(); + var act = () => testSubject.Convert(taintVulnerabilityDto, "C:\\root"); - return new TaintIssueToIssueVisualizationConverter(issueVizConverter, absoluteFilePathLocator); - } + act.Should().Throw(); + issueVizConverter.DidNotReceiveWithAnyArgs().Convert(default); + } - private static SonarQubeIssue CreateServerIssue(string issueKey = "issue key", string filePath = "test.cpp", string hash = "hash", string message = "message", string rule = "rule", - SonarQubeIssueSeverity severity = SonarQubeIssueSeverity.Info, Dictionary cctSeverities = null, IssueTextRange textRange = null, - DateTimeOffset created = default, DateTimeOffset lastUpdate = default, bool resolved = true, string contextKey = null, params IssueFlow[] flows) => - new(issueKey, filePath, hash, message, null, rule, resolved, severity, created, lastUpdate, textRange, flows.ToList(), contextKey, defaultImpacts: cctSeverities); + [TestMethod] + public void MqrSeverity_EmptySeverities_Throws() + { + var taintVulnerabilityDto = CreateDefaultTaintDto(new MQRModeDetails(default, [])); - private static IssueLocation CreateServerLocation(string filePath = "test.cpp", string message = "message", - IssueTextRange textRange = null) => new(filePath, null, textRange, message); + var act = () => testSubject.Convert(taintVulnerabilityDto, "C:\\root"); - private static IssueFlow CreateServerFlow(params IssueLocation[] locations) => new(locations.ToList()); + act.Should().Throw(); + issueVizConverter.DidNotReceiveWithAnyArgs().Convert(default); + } - private static IAnalysisIssueVisualization CreateIssueViz(string serverFilePath = null, params IAnalysisIssueLocationVisualization[] locationVizs) - { - var issueViz = new Mock(); + [DataTestMethod] + [DataRow(ImpactSeverity.BLOCKER, SoftwareQualitySeverity.Blocker)] + [DataRow(ImpactSeverity.HIGH, SoftwareQualitySeverity.High)] + [DataRow(ImpactSeverity.MEDIUM, SoftwareQualitySeverity.Medium)] + [DataRow(ImpactSeverity.LOW, SoftwareQualitySeverity.Low)] + [DataRow(ImpactSeverity.INFO, SoftwareQualitySeverity.Info)] + public void MqrSeverity_SingleSeverity_ReturnsIt( + ImpactSeverity slCoreSeverity, + SoftwareQualitySeverity expectedSeverity) + { + var taintVulnerabilityDto = CreateDefaultTaintDto(new MQRModeDetails(default, [new ImpactDto(SoftwareQuality.SECURITY, slCoreSeverity)])); - var flowViz = new Mock(); - flowViz.Setup(x => x.Locations).Returns(locationVizs); + testSubject.Convert(taintVulnerabilityDto, "C:\\root"); - issueViz.Setup(x => x.Flows).Returns(new[] { flowViz.Object }); - issueViz.SetupGet(x => x.Location.FilePath).Returns(serverFilePath); - issueViz.SetupProperty(x => x.CurrentFilePath); - issueViz.SetupProperty(x => x.IsSuppressed); + issueVizConverter.Received() + .Convert(Arg.Is(x => x.HighestSoftwareQualitySeverity == expectedSeverity && x.Severity == null)); + } - return issueViz.Object; - } + [TestMethod] + public void MqrSeverity_InvalidSeverity_Throws() + { + var taintVulnerabilityDto = CreateDefaultTaintDto(new MQRModeDetails(default, [new ImpactDto(SoftwareQuality.SECURITY, (ImpactSeverity)999)])); - private static IAnalysisIssueLocationVisualization CreateLocationViz(string serverFilePath) - { - var locationViz = new Mock(); - locationViz.SetupGet(x => x.Location.FilePath).Returns(serverFilePath); - locationViz.SetupProperty(x => x.CurrentFilePath); + var act = () => testSubject.Convert(taintVulnerabilityDto, "C:\\root"); - return locationViz.Object; - } + act.Should().Throw(); + } - private SonarQubeIssue CreateDummySonarQubeIssue() - { - return CreateServerIssue(textRange: new IssueTextRange(1, 2, 3, 4)); - } + [DataTestMethod] + [DataRow(new[] { ImpactSeverity.LOW, ImpactSeverity.LOW, ImpactSeverity.LOW }, SoftwareQualitySeverity.Low)] + [DataRow(new[] { ImpactSeverity.LOW, ImpactSeverity.LOW, ImpactSeverity.INFO }, SoftwareQualitySeverity.Low)] + [DataRow(new[] { ImpactSeverity.LOW, ImpactSeverity.MEDIUM, ImpactSeverity.LOW }, SoftwareQualitySeverity.Medium)] + [DataRow(new[] { ImpactSeverity.HIGH, ImpactSeverity.MEDIUM, ImpactSeverity.LOW }, SoftwareQualitySeverity.High)] + [DataRow(new[] { ImpactSeverity.MEDIUM, ImpactSeverity.BLOCKER, ImpactSeverity.HIGH }, SoftwareQualitySeverity.Blocker)] + public void MqrSeverity_MultipleSeverities_ReturnsHighest(ImpactSeverity[] slCoreSeverities, SoftwareQualitySeverity expectedSeverity) + { + var qualities = Enum.GetValues(typeof(SoftwareQuality)).Cast().ToArray(); - private ITaintIssue CreateDummyTaintSonarQubeIssue() + if (slCoreSeverities.Length != qualities.Length) { - return CreateTaintServerIssue("key", "rule", DateTimeOffset.UtcNow, SonarQubeIssueSeverity.Blocker, - mainLocation: CreateTaintServerLocation(serverFilePath: "path", message: "blah", - textRange: CreateTaintTextRange(1, 2, 3, 4, "hash"))); + Assert.Fail("Wrong length of the list"); } - private static ITaintIssue CreateTaintServerIssue(string issueKey = "issue1", - string ruleKey = "rule1", - DateTimeOffset creationDate = default, - SonarQubeIssueSeverity severity = SonarQubeIssueSeverity.Blocker, - Dictionary defaultImpacts = null, - ILocation mainLocation = null, - params IFlow[] flows) - { - var issue = new Mock(); + var taintVulnerabilityDto = CreateDefaultTaintDto(new MQRModeDetails(default, qualities.Zip(slCoreSeverities, (x, y) => new ImpactDto(x, y)).ToList())); - issue.SetupGet(x => x.Key).Returns(issueKey); - issue.SetupGet(x => x.RuleKey).Returns(ruleKey); - issue.SetupGet(x => x.Severity).Returns(severity); - issue.SetupGet(x => x.Flows).Returns(flows); - issue.SetupGet(x => x.CreationDate).Returns(creationDate); - issue.SetupGet(x => x.MainLocation).Returns(mainLocation); - issue.SetupGet(x => x.DefaultImpacts).Returns(defaultImpacts); + testSubject.Convert(taintVulnerabilityDto, "C:\\root"); - return issue.Object; - } - - private static IFlow CreateTaintServerFlow(params ILocation[] locations) - { - var flow = new Mock(); + issueVizConverter.Received() + .Convert(Arg.Is(x => x.HighestSoftwareQualitySeverity == expectedSeverity && x.Severity == null)); + } - flow.SetupGet(x => x.Locations).Returns(locations); + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void Convert_IssueVizIsCorrectlyMarkedAsSuppressed(bool isIssueSuppressed) + { + var taintVulnerabilityDto = CreateDefaultTaintDto(resolved: isIssueSuppressed); + var expectedConvertedIssueViz = CreateIssueViz(); + issueVizConverter.Convert(Arg.Any()) + .Returns(expectedConvertedIssueViz); - return flow.Object; - } + testSubject.Convert(taintVulnerabilityDto, "C:\\root"); - private static ILocation CreateTaintServerLocation(string serverFilePath = "file.cpp", string message = "message", ITextRange textRange = null) - { - var location = new Mock(); + expectedConvertedIssueViz.Received().IsSuppressed = isIssueSuppressed; + } - location.SetupGet(x => x.FilePath).Returns(serverFilePath); - location.SetupGet(x => x.Message).Returns(message); - location.SetupGet(x => x.TextRange).Returns(textRange); + private static TaintVulnerabilityDto CreateDefaultTaintDto(Either severity = null, bool resolved = true) => + new( + Guid.Parse("efa697a2-9cfd-4faf-ba21-71b378667a81"), + "serverkey", + resolved, + "rulekey:S123", + "message1", + "file\\path\\1", + DateTimeOffset.Now, + severity ?? new StandardModeDetails(IssueSeverity.MINOR, RuleType.VULNERABILITY), + [], + new TextRangeWithHashDto(1, 2, 3, 4, "hash1"), + "rulecontext", + false); + + private static IAnalysisIssueVisualization CreateIssueViz(string serverFilePath = null, params IAnalysisIssueLocationVisualization[] locationVizs) + { + var issueViz = Substitute.For(); - return location.Object; - } + var flowViz = Substitute.For(); + flowViz.Locations.Returns(locationVizs); - private static ITextRange CreateTaintTextRange(int startLine, int endLine, int startLineOffset, int endLineOffset, string hash) - { - var textRange = new Mock(); + issueViz.Flows.Returns([flowViz]); - textRange.SetupGet(x => x.StartLine).Returns(startLine); - textRange.SetupGet(x => x.EndLine).Returns(endLine); - textRange.SetupGet(x => x.StartLineOffset).Returns(startLineOffset); - textRange.SetupGet(x => x.EndLineOffset).Returns(endLineOffset); - textRange.SetupGet(x => x.Hash).Returns(hash); + var location = Substitute.For(); + issueViz.Location.Returns(location); + location.FilePath.Returns(serverFilePath); - return textRange.Object; - } + return issueViz; } } diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs index a71686cdd..0841d6457 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintIssuesSynchronizerTests.cs @@ -1,587 +1,587 @@ -/* - * 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; - -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 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); +// } +// } +// } diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.Caption.cs b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.Caption.cs index f458b4c5f..ff3fd84cf 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.Caption.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.Caption.cs @@ -27,6 +27,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using Microsoft.VisualStudio.Text; using Moq; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Core.Telemetry; using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.Infrastructure.VS.DocumentEvents; @@ -259,6 +260,7 @@ private static IAnalysisIssueVisualization CreateIssueViz(string filePath = "tes var issue = new Mock(); var issueViz = new Mock(); + issue.Setup(x => x.Severity).Returns(AnalysisIssueSeverity.Major); issueViz.Setup(x => x.CurrentFilePath).Returns(filePath); issueViz.Setup(x => x.Issue).Returns(issue.Object); issueViz.Setup(x => x.Flows).Returns(Array.Empty()); diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs index 9af9baaf5..95e90b333 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintList/TaintIssuesControlViewModelTests.cs @@ -30,6 +30,7 @@ using Microsoft.VisualStudio.Text; using Moq; using SonarLint.VisualStudio.Core; +using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.Core.Telemetry; using SonarLint.VisualStudio.Infrastructure.VS; using SonarLint.VisualStudio.Infrastructure.VS.DocumentEvents; @@ -928,6 +929,7 @@ private IAnalysisIssueVisualization CreateIssueViz(string filePath = "test.cpp", DateTimeOffset created = default, bool isSuppressed = false, params IAnalysisIssueLocationVisualization[] locations) { var issue = new Mock(); + issue.Setup(x => x.Severity).Returns(AnalysisIssueSeverity.Major); issue.Setup(x => x.IssueKey).Returns(issueKey); issue.Setup(x => x.CreationTimestamp).Returns(created); diff --git a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs index 67502ed20..167bc740c 100644 --- a/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs +++ b/src/IssueViz.Security.UnitTests/Taint/TaintStoreTests.cs @@ -55,7 +55,7 @@ public void MefCtor_CheckExports() storeImport.Import.Should().BeSameAs(issuesStoreImport.Import); } - + [TestMethod] public void GetAll_ReturnsImmutableInstance() { diff --git a/src/IssueViz.Security/Taint/Models/ITaintIssue.cs b/src/IssueViz.Security/Taint/Models/ITaintIssue.cs index 2f2a28dfd..6971814f7 100644 --- a/src/IssueViz.Security/Taint/Models/ITaintIssue.cs +++ b/src/IssueViz.Security/Taint/Models/ITaintIssue.cs @@ -18,36 +18,31 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using SonarLint.VisualStudio.Core.Analysis; namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models { - internal interface ITaintIssue : IAnalysisIssueBase + public interface ITaintIssue : IAnalysisIssueBase { string IssueKey { get; } - AnalysisIssueSeverity Severity { get; } - + AnalysisIssueSeverity? Severity { get; } + SoftwareQualitySeverity? HighestSoftwareQualitySeverity { get; } DateTimeOffset CreationTimestamp { get; } - - DateTimeOffset LastUpdateTimestamp { get; } } - internal class TaintIssue : ITaintIssue + public class TaintIssue : ITaintIssue { private static readonly IReadOnlyList EmptyFlows = Array.Empty(); public TaintIssue(string issueKey, string ruleKey, IAnalysisIssueLocation primaryLocation, - AnalysisIssueSeverity severity, + AnalysisIssueSeverity? severity, SoftwareQualitySeverity? highestSoftwareQualitySeverity, DateTimeOffset creationTimestamp, - DateTimeOffset lastUpdateTimestamp, IReadOnlyList flows, string ruleDescriptionContextKey) { @@ -56,18 +51,21 @@ public TaintIssue(string issueKey, PrimaryLocation = primaryLocation ?? throw new ArgumentNullException(nameof(primaryLocation)); Severity = severity; CreationTimestamp = creationTimestamp; - LastUpdateTimestamp = lastUpdateTimestamp; Flows = flows ?? EmptyFlows; RuleDescriptionContextKey = ruleDescriptionContextKey; - this.HighestSoftwareQualitySeverity = highestSoftwareQualitySeverity; + HighestSoftwareQualitySeverity = highestSoftwareQualitySeverity; + + if (!severity.HasValue && !highestSoftwareQualitySeverity.HasValue) + { + throw new ArgumentException(string.Format(TaintResources.TaintIssue_SeverityUndefined, IssueKey)); + } } public string IssueKey { get; } public string RuleKey { get; } - public AnalysisIssueSeverity Severity { get; } + public AnalysisIssueSeverity? Severity { get; } public SoftwareQualitySeverity? HighestSoftwareQualitySeverity { get; } public DateTimeOffset CreationTimestamp { get; } - public DateTimeOffset LastUpdateTimestamp { get; } public IReadOnlyList Flows { get; } public IAnalysisIssueLocation PrimaryLocation { get; } public string RuleDescriptionContextKey { get; } diff --git a/src/IssueViz.Security/Taint/ServerSentEvents/TaintServerEventsListener.cs b/src/IssueViz.Security/Taint/ServerSentEvents/TaintServerEventsListener.cs index 6903c9571..9e409a27d 100644 --- a/src/IssueViz.Security/Taint/ServerSentEvents/TaintServerEventsListener.cs +++ b/src/IssueViz.Security/Taint/ServerSentEvents/TaintServerEventsListener.cs @@ -109,13 +109,15 @@ public async Task ListenAsync() private async Task AddToStoreIfOnTheRightBranchAsync(ITaintVulnerabilityRaisedServerEvent taintRaisedEvent) { - var serverBranch = await serverBranchProvider.GetServerBranchNameAsync(cancellationTokenSource.Token); + // todo https://sonarsource.atlassian.net/browse/SLVS-1593 - if (taintRaisedEvent.Branch.Equals(serverBranch)) - { - var taintIssue = taintToIssueVizConverter.Convert(taintRaisedEvent.Issue); - taintStore.Add(taintIssue); - } + // var serverBranch = await serverBranchProvider.GetServerBranchNameAsync(cancellationTokenSource.Token); + // + // if (taintRaisedEvent.Branch.Equals(serverBranch)) + // { + // var taintIssue = taintToIssueVizConverter.Convert(taintRaisedEvent.Issue); + // taintStore.Add(taintIssue); + // } } private bool disposed; diff --git a/src/IssueViz.Security/Taint/TaintIssueToIssueVisualizationConverter.cs b/src/IssueViz.Security/Taint/TaintIssueToIssueVisualizationConverter.cs index 7424e9571..2b859578c 100644 --- a/src/IssueViz.Security/Taint/TaintIssueToIssueVisualizationConverter.cs +++ b/src/IssueViz.Security/Taint/TaintIssueToIssueVisualizationConverter.cs @@ -18,224 +18,74 @@ * 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 System.IO; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Core.Analysis; using SonarLint.VisualStudio.IssueVisualization.Models; using SonarLint.VisualStudio.IssueVisualization.Security.Taint.Models; -using SonarQube.Client.Models; -using SonarQube.Client.Models.ServerSentEvents.ClientContract; -using ITaintIssue = SonarQube.Client.Models.ServerSentEvents.ClientContract.ITaintIssue; +using SonarLint.VisualStudio.SLCore.Common.Helpers; +using SonarLint.VisualStudio.SLCore.Common.Models; -namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint -{ - internal interface ITaintIssueToIssueVisualizationConverter - { - IAnalysisIssueVisualization Convert(SonarQubeIssue sonarQubeIssue); +namespace SonarLint.VisualStudio.IssueVisualization.Security.Taint; - IAnalysisIssueVisualization Convert(ITaintIssue sonarQubeTaintIssue); - } +internal interface ITaintIssueToIssueVisualizationConverter +{ + IAnalysisIssueVisualization Convert(TaintVulnerabilityDto slcoreTaintIssue, string configScopeRoot); +} - [Export(typeof(ITaintIssueToIssueVisualizationConverter))] - internal class TaintIssueToIssueVisualizationConverter : ITaintIssueToIssueVisualizationConverter +[Export(typeof(ITaintIssueToIssueVisualizationConverter))] +[PartCreationPolicy(CreationPolicy.Shared)] +[method: ImportingConstructor] +internal class TaintIssueToIssueVisualizationConverter(IAnalysisIssueVisualizationConverter issueVisualizationConverter) + : ITaintIssueToIssueVisualizationConverter +{ + public IAnalysisIssueVisualization Convert(TaintVulnerabilityDto slcoreTaintIssue, string configScopeRoot) { - private readonly IAnalysisIssueVisualizationConverter issueVisualizationConverter; - private readonly IAbsoluteFilePathLocator absoluteFilePathLocator; - - [ImportingConstructor] - public TaintIssueToIssueVisualizationConverter(IAnalysisIssueVisualizationConverter issueVisualizationConverter, IAbsoluteFilePathLocator absoluteFilePathLocator) - { - this.issueVisualizationConverter = issueVisualizationConverter; - this.absoluteFilePathLocator = absoluteFilePathLocator; - } - - public IAnalysisIssueVisualization Convert(SonarQubeIssue sonarQubeIssue) - { - var analysisIssue = ConvertToAnalysisIssue(sonarQubeIssue); - var issueViz = CreateAnalysisIssueVisualization(analysisIssue); - issueViz.IsSuppressed = sonarQubeIssue.IsResolved; - - return issueViz; - } - - public IAnalysisIssueVisualization Convert(ITaintIssue sonarQubeTaintIssue) - { - var analysisIssue = ConvertToAnalysisIssue(sonarQubeTaintIssue); - - return CreateAnalysisIssueVisualization(analysisIssue); - } - - private IAnalysisIssueVisualization CreateAnalysisIssueVisualization(IAnalysisIssueBase analysisIssue) - { - var issueViz = issueVisualizationConverter.Convert(analysisIssue); - - CalculateLocalFilePaths(issueViz); - - return issueViz; - } - - private void CalculateLocalFilePaths(IAnalysisIssueVisualization issueViz) - { - var allLocations = issueViz.GetAllLocations(); - - foreach (var location in allLocations) - { - location.CurrentFilePath = absoluteFilePathLocator.Locate(location.Location.FilePath); - } - } - - private static IAnalysisIssueBase ConvertToAnalysisIssue(SonarQubeIssue sonarQubeIssue) - { - if (sonarQubeIssue.TextRange == null) - { - throw new ArgumentNullException(nameof(sonarQubeIssue.TextRange)); - } + var analysisIssue = ConvertToAnalysisIssue(slcoreTaintIssue, configScopeRoot); + var issueViz = CreateAnalysisIssueVisualization(analysisIssue); + issueViz.IsSuppressed = slcoreTaintIssue.resolved; - return new TaintIssue( - sonarQubeIssue.IssueKey, - sonarQubeIssue.RuleId, - primaryLocation: new AnalysisIssueLocation( - sonarQubeIssue.Message, - sonarQubeIssue.FilePath, - textRange: new TextRange( - sonarQubeIssue.TextRange.StartLine, - sonarQubeIssue.TextRange.EndLine, - sonarQubeIssue.TextRange.StartOffset, - sonarQubeIssue.TextRange.EndOffset, - sonarQubeIssue.Hash)), - Convert(sonarQubeIssue.Severity), - ConvertToHighestSeverity(sonarQubeIssue.DefaultImpacts), - sonarQubeIssue.CreationTimestamp, - sonarQubeIssue.LastUpdateTimestamp, - Convert(sonarQubeIssue.Flows), - sonarQubeIssue.Context - ); - } - - private static IAnalysisIssueBase ConvertToAnalysisIssue(ITaintIssue sonarQubeTaintIssue) - { - return new TaintIssue( - sonarQubeTaintIssue.Key, - sonarQubeTaintIssue.RuleKey, - primaryLocation: new AnalysisIssueLocation( - sonarQubeTaintIssue.MainLocation.Message, - sonarQubeTaintIssue.MainLocation.FilePath, - textRange: new TextRange( - sonarQubeTaintIssue.MainLocation.TextRange.StartLine, - sonarQubeTaintIssue.MainLocation.TextRange.EndLine, - sonarQubeTaintIssue.MainLocation.TextRange.StartLineOffset, - sonarQubeTaintIssue.MainLocation.TextRange.EndLineOffset, - sonarQubeTaintIssue.MainLocation.TextRange.Hash)), - Convert(sonarQubeTaintIssue.Severity), - ConvertToHighestSeverity(sonarQubeTaintIssue.DefaultImpacts), - sonarQubeTaintIssue.CreationDate, - default, - Convert(sonarQubeTaintIssue.Flows), - sonarQubeTaintIssue.Context - ); - } - - private static IReadOnlyList Convert(IEnumerable flows) => - flows.Select(x => new AnalysisIssueFlow(Convert(x.Locations))).ToArray(); - - private static IReadOnlyList Convert(IEnumerable flows) => - flows.Select(x => new AnalysisIssueFlow(Convert(x.Locations))).ToArray(); - - private static IReadOnlyList Convert(IEnumerable locations) => - locations.Reverse().Select(location => - { - if (location.TextRange == null) - { - throw new ArgumentNullException(nameof(location.TextRange)); - } - - return new AnalysisIssueLocation(location.Message, - location.FilePath, - textRange: new TextRange( - location.TextRange.StartLine, - location.TextRange.EndLine, - location.TextRange.StartOffset, - location.TextRange.EndOffset, - null)); - }).ToArray(); - - private static IReadOnlyList Convert(IEnumerable locations) => - locations.Reverse().Select(location => - { - if (location.TextRange == null) - { - throw new ArgumentNullException(nameof(location.TextRange)); - } - - return new AnalysisIssueLocation(location.Message, - location.FilePath, - textRange: new TextRange( - location.TextRange.StartLine, - location.TextRange.EndLine, - location.TextRange.StartLineOffset, - location.TextRange.EndLineOffset, - location.TextRange.Hash)); - }).ToArray(); - - /// - /// Converts from the sonarqube issue severity enum to the standard AnalysisIssueSeverity - /// - internal /* for testing */ static AnalysisIssueSeverity Convert(SonarQubeIssueSeverity issueSeverity) - { - switch (issueSeverity) - { - case SonarQubeIssueSeverity.Blocker: - return AnalysisIssueSeverity.Blocker; - - case SonarQubeIssueSeverity.Critical: - return AnalysisIssueSeverity.Critical; - - case SonarQubeIssueSeverity.Info: - return AnalysisIssueSeverity.Info; - - case SonarQubeIssueSeverity.Major: - return AnalysisIssueSeverity.Major; - - case SonarQubeIssueSeverity.Minor: - return AnalysisIssueSeverity.Minor; - - default: - throw new ArgumentOutOfRangeException(nameof(issueSeverity)); - } - } - - internal /* for testing */ static SoftwareQualitySeverity? ConvertToHighestSeverity( - Dictionary sonarQubeSoftwareQualitySeverities) - { - if (sonarQubeSoftwareQualitySeverities == null || sonarQubeSoftwareQualitySeverities.Count == 0) - { - return null; - } - - return sonarQubeSoftwareQualitySeverities - .Select(kvp => kvp.Value) - .Select(sqSeverity => - { - switch (sqSeverity) - { - case SonarQubeSoftwareQualitySeverity.Info: - return SoftwareQualitySeverity.Info; - case SonarQubeSoftwareQualitySeverity.Low: - return SoftwareQualitySeverity.Low; - case SonarQubeSoftwareQualitySeverity.Medium: - return SoftwareQualitySeverity.Medium; - case SonarQubeSoftwareQualitySeverity.High: - return SoftwareQualitySeverity.High; - case SonarQubeSoftwareQualitySeverity.Blocker: - return SoftwareQualitySeverity.Blocker; - default: - throw new ArgumentOutOfRangeException(nameof(sqSeverity)); - } - }) - .Max(); - } + return issueViz; } + + private static IAnalysisIssueBase ConvertToAnalysisIssue(TaintVulnerabilityDto slcoreTaintIssue, string configScopeRoot) => + new TaintIssue( + slcoreTaintIssue.sonarServerKey, + slcoreTaintIssue.ruleKey, + CreateLocation(slcoreTaintIssue.message, slcoreTaintIssue.ideFilePath, configScopeRoot, slcoreTaintIssue.textRange), + slcoreTaintIssue.severityMode.Left?.severity.ToAnalysisIssueSeverity(), + slcoreTaintIssue.severityMode.Right?.impacts.Select(x => x?.impactSeverity.ToSoftwareQualitySeverity()).Max(), + slcoreTaintIssue.introductionDate, + slcoreTaintIssue + .flows + .Select(taintFlow => + new AnalysisIssueFlow( + taintFlow + .locations + .Select(taintLocation => + CreateLocation( + taintLocation.message, + taintLocation.filePath, + configScopeRoot, + taintLocation.textRange)) + .ToArray())) + .ToArray(), + slcoreTaintIssue.ruleDescriptionContextKey); + + private static AnalysisIssueLocation CreateLocation( + string message, + string filePath, + string configScopeRoot, + TextRangeWithHashDto textRange) => + new(message, + Path.Combine(configScopeRoot, filePath), + new TextRange(textRange.startLine, + textRange.endLine, + textRange.startLineOffset, + textRange.endLineOffset, + textRange.hash)); + + private IAnalysisIssueVisualization CreateAnalysisIssueVisualization(IAnalysisIssueBase analysisIssue) => + issueVisualizationConverter.Convert(analysisIssue); } diff --git a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs index c8eafbcad..826aca119 100644 --- a/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs +++ b/src/IssueViz.Security/Taint/TaintIssuesSynchronizer.cs @@ -79,50 +79,52 @@ public TaintIssuesSynchronizer(ITaintStore taintStore, public async Task SynchronizeWithServer() { - 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(); - } + 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(); + // } } private bool IsStandalone(BindingConfiguration bindingConfiguration) diff --git a/src/IssueViz.Security/Taint/TaintResources.Designer.cs b/src/IssueViz.Security/Taint/TaintResources.Designer.cs index 9e593c89c..1e52382a0 100644 --- a/src/IssueViz.Security/Taint/TaintResources.Designer.cs +++ b/src/IssueViz.Security/Taint/TaintResources.Designer.cs @@ -1,7 +1,6 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -123,5 +122,14 @@ internal static string SyncPackage_Initializing { return ResourceManager.GetString("SyncPackage_Initializing", resourceCulture); } } + + /// + /// Looks up a localized string similar to Taint issue with id: {0} has no defined severity. + /// + internal static string TaintIssue_SeverityUndefined { + get { + return ResourceManager.GetString("TaintIssue_SeverityUndefined", resourceCulture); + } + } } } diff --git a/src/IssueViz.Security/Taint/TaintResources.resx b/src/IssueViz.Security/Taint/TaintResources.resx index 4f77c8034..9cb200091 100644 --- a/src/IssueViz.Security/Taint/TaintResources.resx +++ b/src/IssueViz.Security/Taint/TaintResources.resx @@ -139,4 +139,7 @@ [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 + \ No newline at end of file diff --git a/src/IssueViz.Security/Taint/TaintStore.cs b/src/IssueViz.Security/Taint/TaintStore.cs index 0fd72a7f0..7120f10c4 100644 --- a/src/IssueViz.Security/Taint/TaintStore.cs +++ b/src/IssueViz.Security/Taint/TaintStore.cs @@ -180,7 +180,7 @@ public bool Equals(IAnalysisIssueVisualization first, IAnalysisIssueVisualizatio return firstTaintIssue.IssueKey.Equals(secondTaintIssue.IssueKey); } - + public int GetHashCode(IAnalysisIssueVisualization obj) { diff --git a/src/SLCore.UnitTests/Common/Models/TaintVulnerabilityDtoTests.cs b/src/SLCore.UnitTests/Common/Models/TaintVulnerabilityDtoTests.cs index e9c3a662c..f3a8a91f3 100644 --- a/src/SLCore.UnitTests/Common/Models/TaintVulnerabilityDtoTests.cs +++ b/src/SLCore.UnitTests/Common/Models/TaintVulnerabilityDtoTests.cs @@ -41,7 +41,7 @@ public void Deserialized_AsExpected() new MQRModeDetails(CleanCodeAttribute.COMPLETE, [new ImpactDto(SoftwareQuality.SECURITY, ImpactSeverity.HIGH)]), [ new TaintFlowDto([ - new TaintLocationDto(new TextRangeWithHashDto(20, 32, 20, 58, "f677236678ac4b2ab451d66d4b251e8f"), + new TaintFlowLocationDto(new TextRangeWithHashDto(20, 32, 20, 58, "f677236678ac4b2ab451d66d4b251e8f"), "Sink: this invocation is not safe; a malicious value can be injected into the caller", "sonarlint-visualstudio-sampleprojects\\bound\\sonarcloud\\SLVS_Samples_Bound_VS2019\\Taint_CSharp_NetCore_WebAppReact\\Taint\\XmlSerializerInjectionController.cs") ]) diff --git a/src/SLCore/Common/Models/TaintVulnerabilityDto.cs b/src/SLCore/Common/Models/TaintVulnerabilityDto.cs index fb3ba41e5..4f0ab3a9f 100644 --- a/src/SLCore/Common/Models/TaintVulnerabilityDto.cs +++ b/src/SLCore/Common/Models/TaintVulnerabilityDto.cs @@ -39,6 +39,6 @@ public record TaintVulnerabilityDto( string ruleDescriptionContextKey, bool isOnNewCode); -public record TaintFlowDto(List locations); +public record TaintFlowDto(List locations); -public record TaintLocationDto(TextRangeWithHashDto textRange, string message, string filePath); +public record TaintFlowLocationDto(TextRangeWithHashDto textRange, string message, string filePath);