From 490d3fae8c4c12aa43c266878ff4368e84c2e0cb Mon Sep 17 00:00:00 2001 From: Gabriela Trutan Date: Tue, 10 Dec 2024 12:12:29 +0100 Subject: [PATCH] SLVS-1687 CaYC touched classes: -replace Moq -use testInitialize -format and cleanup code --- .../Suppressions/ServerIssuesStoreTests.cs | 472 ++++++------- .../SuppressionIssueStoreUpdaterTests.cs | 646 ++++++++---------- .../Suppressions/IServerIssuesStore.cs | 2 - .../SuppressionIssueStoreUpdater.cs | 12 +- 4 files changed, 499 insertions(+), 633 deletions(-) diff --git a/src/ConnectedMode.UnitTests/Suppressions/ServerIssuesStoreTests.cs b/src/ConnectedMode.UnitTests/Suppressions/ServerIssuesStoreTests.cs index c0f075427..616c3c884 100644 --- a/src/ConnectedMode.UnitTests/Suppressions/ServerIssuesStoreTests.cs +++ b/src/ConnectedMode.UnitTests/Suppressions/ServerIssuesStoreTests.cs @@ -18,329 +18,281 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.VisualStudio.RemoteSettings; using SonarLint.VisualStudio.ConnectedMode.Suppressions; using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.TestInfrastructure; using SonarQube.Client.Models; -namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Suppressions +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Suppressions; + +[TestClass] +public class ServerIssuesStoreTests { - [TestClass] - public class ServerIssuesStoreTests + private readonly SonarQubeIssue resolvedIssue = CreateIssue("3", true); + private readonly SonarQubeIssue resolvedIssue2 = CreateIssue("4", true); + private readonly SonarQubeIssue unresolvedIssue = CreateIssue("1"); + private readonly SonarQubeIssue unresolvedIssue2 = CreateIssue("2"); + private ILogger logger; + private ServerIssuesStore testSubject; + + [TestInitialize] + public void TestInitialize() { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport()); - - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport()); - } - - [TestMethod] - public void MefCtor_Check_SameInstanceExported() - => MefTestHelpers.CheckMultipleExportsReturnSameInstance( - MefTestHelpers.CreateExport()); - - [TestMethod] - public void TryGetIssue_HasIssueWithSameKey_ReturnsTrue() - { - var issue = CreateIssue("1", false); - - var testSubject = CreateTestSubject(); - testSubject.AddIssues(new []{issue}, false); - - testSubject.TryGetIssue(issue.IssueKey, out var storedIssue).Should().BeTrue(); - storedIssue.Should().BeSameAs(issue); - } - - [TestMethod] - public void TryGetIssue_NoMatchingIssue_ReturnsFalse() - { - var issue = CreateIssue("1", false); - - var testSubject = CreateTestSubject(); - testSubject.AddIssues(new []{issue}, false); - - testSubject.TryGetIssue("NOTMATCHINGKEY", out var storedIssue).Should().BeFalse(); - } - - [TestMethod] - [DataRow(false)] - [DataRow(true)] - public void AddIssues_EmptyList_ResultContainsNewIssues(bool clearAllExistingIssues) - { - var issue1 = CreateIssue("1", false); - var issue2 = CreateIssue("2", false); - - var testSubject = CreateTestSubject(); - - var result = testSubject.Get(); - result.Count().Should().Be(0); - - testSubject.AddIssues(new List() { issue1, issue2 }, clearAllExistingIssues: clearAllExistingIssues); - - result = testSubject.Get(); - result.Count().Should().Be(2); - result.Should().Contain(issue1); - result.Should().Contain(issue2); - } - - [TestMethod] - public void AddIssues_NonEmptyList_ClearExisting_OldIssuesCleared() - { - var issue1 = CreateIssue("1", false); - var issue2 = CreateIssue("2", false); + logger = new TestLogger(); + testSubject = new ServerIssuesStore(logger); + } - var testSubject = CreateTestSubject(); + [TestMethod] + public void MefCtor_CheckIsExported() + { + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport()); - var result = testSubject.Get(); - result.Count().Should().Be(0); + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport()); + } - testSubject.AddIssues(new List() { issue1 }, clearAllExistingIssues: false); + [TestMethod] + public void MefCtor_Check_SameInstanceExported() => + MefTestHelpers.CheckMultipleExportsReturnSameInstance( + MefTestHelpers.CreateExport()); - result = testSubject.Get(); - result.Count().Should().Be(1); - result.Should().Contain(issue1); + [TestMethod] + public void TryGetIssue_HasIssueWithSameKey_ReturnsTrue() + { + InitializeStoreWithIssue(unresolvedIssue); - testSubject.AddIssues(new List() { issue2 }, clearAllExistingIssues: true); + testSubject.TryGetIssue(unresolvedIssue.IssueKey, out var storedIssue).Should().BeTrue(); - result = testSubject.Get(); - result.Count().Should().Be(1); - result.Should().Contain(issue2); - } + storedIssue.Should().BeSameAs(unresolvedIssue); + } - [TestMethod] - public void AddIssues_ListOfAddIssues_NonEmptyList_DontClearExisting_OldIssuesAreRetained() - { - var issue1 = CreateIssue("1", false); - var issue2 = CreateIssue("2", false); + [TestMethod] + public void TryGetIssue_NoMatchingIssue_ReturnsFalse() + { + InitializeStoreWithIssue(unresolvedIssue); - var testSubject = CreateTestSubject(); + testSubject.TryGetIssue("NOTMATCHINGKEY", out _).Should().BeFalse(); + } - var result = testSubject.Get(); - result.Count().Should().Be(0); + [TestMethod] + [DataRow(false)] + [DataRow(true)] + public void AddIssues_EmptyList_ResultContainsNewIssues(bool clearAllExistingIssues) + { + VerifyNoIssues(); - testSubject.AddIssues(new List() { issue1 }, clearAllExistingIssues: false); + testSubject.AddIssues([unresolvedIssue, unresolvedIssue2], clearAllExistingIssues); - result = testSubject.Get(); - result.Count().Should().Be(1); - result.Should().Contain(issue1); + VerifyExpectedIssues(unresolvedIssue, unresolvedIssue2); + } - testSubject.AddIssues(new List() { issue2 }, clearAllExistingIssues: false); + [TestMethod] + public void AddIssues_NonEmptyList_ClearExisting_OldIssuesCleared() + { + VerifyNoIssues(); - result = testSubject.Get(); - result.Count().Should().Be(2); - result.Should().Contain(issue1); - result.Should().Contain(issue2); - } + testSubject.AddIssues([unresolvedIssue], false); + VerifyExpectedIssues(unresolvedIssue); - [TestMethod] - public void AddIssues_ExistingIssues_NewIssuesUpdateExistingIssues() - { - var testSubject = CreateTestSubject(); - - var existing1 = CreateIssue("e1"); - var existing2 = CreateIssue("e2"); - - // Set the initial list of items - testSubject.AddIssues(new[] { existing1, existing2 }, true); - var initialIssues = testSubject.Get(); - initialIssues.Should().HaveCount(2); - initialIssues.Should().BeEquivalentTo(existing1, existing2); - - // Modify the items in the store - // e1 is unchanged - // e2 is replaced - // n1 is added - // E1 is added - different case from "e1" so should be treated as different - var mod1 = CreateIssue("e2"); - var mod2 = CreateIssue("e3"); - var new1 = CreateIssue("n1"); - var new2 = CreateIssue("E1"); - - testSubject.AddIssues(new[] { mod1, mod2, new1, new2 }, false); - - var actual = testSubject.Get(); - actual.Should().HaveCount(5); - actual.Should().BeEquivalentTo(existing1, mod1, mod2, new1, new2); - - actual.Should().NotContain(existing2); - } + testSubject.AddIssues([unresolvedIssue2], true); + VerifyExpectedIssues(unresolvedIssue2); + } - [TestMethod] - public void AddIssues_AddNullIssues_EventIsNotInvoked() - { - var testSubject = CreateTestSubject(); + [TestMethod] + public void AddIssues_ListOfAddIssues_NonEmptyList_DontClearExisting_OldIssuesAreRetained() + { + VerifyNoIssues(); - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + testSubject.AddIssues([unresolvedIssue], false); + VerifyExpectedIssues(unresolvedIssue); - testSubject.AddIssues(null, false); + testSubject.AddIssues([unresolvedIssue2], false); + VerifyExpectedIssues(unresolvedIssue, unresolvedIssue2); + } - eventMock.VerifyNoOtherCalls(); - } + [TestMethod] + public void AddIssues_ExistingIssues_NewIssuesUpdateExistingIssues() + { + var existing1 = CreateIssue("e1"); + var existing2 = CreateIssue("e2"); + // Set the initial list of items + testSubject.AddIssues([existing1, existing2], true); + VerifyExpectedIssues(existing1, existing2); + + // Modify the items in the store + // e1 is unchanged + // e2 is replaced + // n1 is added + // E1 is added - different case from "e1" so should be treated as different + var mod1 = CreateIssue("e2"); + var mod2 = CreateIssue("e3"); + var new1 = CreateIssue("n1"); + var new2 = CreateIssue("E1"); + + testSubject.AddIssues([mod1, mod2, new1, new2], false); + + VerifyExpectedIssues(existing1, mod1, mod2, new1, new2); + testSubject.Get().Should().NotContain(existing2); + } - [TestMethod] - [DataRow(true)] - [DataRow(false)] - public void AddIssues_EventIsInvoked(bool clearAllExistingIssues) - { - var issue1 = CreateIssue("issue1", true); + [TestMethod] + public void AddIssues_AddNullIssues_EventIsNotInvoked() + { + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - var testSubject = CreateTestSubject(); - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + testSubject.AddIssues(null, false); - testSubject.AddIssues(new List() { issue1 }, clearAllExistingIssues); - eventMock.Verify(x => x(testSubject, EventArgs.Empty), Times.Once); - } + VerifyNotRaisedServerIssueChanged(eventMock); + } - [TestMethod] - public void UpdateIssues_EmptyList_DoesNotInvokeEvent() - { - var testSubject = CreateTestSubject(); + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public void AddIssues_EventIsInvoked(bool clearAllExistingIssues) + { + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + testSubject.AddIssues([unresolvedIssue], clearAllExistingIssues); - var result = testSubject.Get(); - result.Count().Should().Be(0); - testSubject.UpdateIssues(false, new[] { "issue1" }); + VerifyRaisedServerIssueChanged(eventMock); + } - eventMock.VerifyNoOtherCalls(); - } + [TestMethod] + public void UpdateIssues_EmptyList_DoesNotInvokeEvent() + { + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; + VerifyNoIssues(); - [TestMethod] - public void UpdateIssues_NonEmptyList_NoMatch_DoesNotInvokeEvent() - { - var testSubject = CreateTestSubject(); - testSubject.AddIssues(new List() { CreateIssue("issue1", true) }, clearAllExistingIssues: false); + testSubject.UpdateIssues(false, [resolvedIssue.IssueKey]); - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; - testSubject.UpdateIssues(false, new[] { "issue2" }); + VerifyNotRaisedServerIssueChanged(eventMock); + } - eventMock.VerifyNoOtherCalls(); - } + [TestMethod] + public void UpdateIssues_NonEmptyList_NoMatch_DoesNotInvokeEvent() + { + InitializeStoreWithIssue(unresolvedIssue); + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - [TestMethod] - public void UpdateIssues_NonEmptyList_Match_InvokesEventAndPropertyIsChanged() - { - var issue1 = CreateIssue("issue1", true); - var issue2 = CreateIssue("issue2", true); // this property should be changed - var issue3 = CreateIssue("issue3", false); + testSubject.UpdateIssues(false, ["NOTMATCHINGKEY"]); - var testSubject = CreateTestSubject(); - testSubject.AddIssues(new List() { issue1, issue2, issue3 }, clearAllExistingIssues: false); + VerifyNotRaisedServerIssueChanged(eventMock); + } - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + [TestMethod] + public void UpdateIssues_NonEmptyList_Match_InvokesEventAndPropertyIsChanged() + { + InitializeStoreWithIssue(resolvedIssue, resolvedIssue2, unresolvedIssue); + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - testSubject.UpdateIssues(false, new[] { "issue2", "issue3" }); + testSubject.UpdateIssues(false, [resolvedIssue2.IssueKey, unresolvedIssue.IssueKey]); - issue1.IsResolved.Should().BeTrue(); - issue2.IsResolved.Should().BeFalse(); - issue3.IsResolved.Should().BeFalse(); + resolvedIssue.IsResolved.Should().BeTrue(); + resolvedIssue2.IsResolved.Should().BeFalse(); + unresolvedIssue.IsResolved.Should().BeFalse(); - // Changing a single property should be enough to trigger the event - eventMock.Verify(x => x(testSubject, EventArgs.Empty), Times.Exactly(1)); - } + // Changing a single property should be enough to trigger the event + VerifyRaisedServerIssueChanged(eventMock); + } - [TestMethod] - [DataRow(true, true)] - [DataRow(false, false)] - [DataRow(true, false)] - [DataRow(false, true)] - public void UpdateIssues_IssueExists_InvokesEventIfPropertyDoesNotMatch(bool storeValue, bool newValue) - { - var issue1 = CreateIssue("issue1Key", storeValue); + [TestMethod] + [DataRow(true, true)] + [DataRow(false, false)] + [DataRow(true, false)] + [DataRow(false, true)] + public void UpdateIssues_IssueExists_InvokesEventIfPropertyDoesNotMatch(bool storeValue, bool newValue) + { + var issue1 = CreateIssue("issue1Key", storeValue); + InitializeStoreWithIssue(issue1); + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - var testSubject = CreateTestSubject(); - testSubject.AddIssues(new List() { issue1 }, clearAllExistingIssues: true); + testSubject.UpdateIssues(newValue, [issue1.IssueKey]); - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + issue1.IsResolved.Should().Be(newValue); + var expectedEventCount = storeValue == newValue ? 0 : 1; + eventMock.Received(expectedEventCount).Invoke(testSubject, Arg.Is(x => x == EventArgs.Empty)); + } - testSubject.UpdateIssues(newValue, new[] { "issue1Key" }); + [TestMethod] + public void Reset_HasIssue_AllIssuesRemoved() + { + InitializeStoreWithIssue(unresolvedIssue); - issue1.IsResolved.Should().Be(newValue); + testSubject.Reset(); - var expectedEventCount = (storeValue == newValue) ? 0 : 1; - eventMock.Verify(x => x(testSubject, EventArgs.Empty), Times.Exactly(expectedEventCount)); - } + testSubject.Get().Should().BeEmpty(); + } - [TestMethod] - public void Reset_HasIssue_AllIssuesRemoved() - { - var testSubject = InitializeStoreWithOneIssue(); + [TestMethod] + public void Reset_HasIssue_InvokesEvent() + { + InitializeStoreWithIssue(unresolvedIssue); + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - testSubject.Reset(); + testSubject.Reset(); - testSubject.Get().Should().BeEmpty(); - } + VerifyRaisedServerIssueChanged(eventMock); + } - [TestMethod] - public void Reset_HasIssue_InvokesEvent() - { - var testSubject = InitializeStoreWithOneIssue(); - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + [TestMethod] + public void Reset_NoIssue_DoesNotInvokeEvent() + { + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - testSubject.Reset(); + testSubject.Reset(); - eventMock.Verify(x => x(testSubject, EventArgs.Empty), Times.Exactly(1)); - } + VerifyNotRaisedServerIssueChanged(eventMock); + } - [TestMethod] - public void Reset_NoIssue_DoesNotInvokeEvent() - { - var testSubject = CreateTestSubject(); - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + [TestMethod] + public void Reset_CalledMultipleTimes_InvokesEventOnce() + { + InitializeStoreWithIssue(unresolvedIssue); + var eventMock = Substitute.For(); + testSubject.ServerIssuesChanged += eventMock; - testSubject.Reset(); + testSubject.Reset(); + testSubject.Reset(); + testSubject.Reset(); - eventMock.Verify(x => x(testSubject, EventArgs.Empty), Times.Never); - } + VerifyRaisedServerIssueChanged(eventMock); + } - [TestMethod] - public void Reset_CalledMultipleTimes_InvokesEventOnce() - { - var testSubject = InitializeStoreWithOneIssue(); - var eventMock = new Mock(); - testSubject.ServerIssuesChanged += eventMock.Object; + private static SonarQubeIssue CreateIssue(string key, bool isResolved = false) + { + var issue = new SonarQubeIssue(key, "", "", "", "", "", isResolved, SonarQubeIssueSeverity.Info, DateTimeOffset.MinValue, DateTimeOffset.MinValue, null, null); - testSubject.Reset(); - testSubject.Reset(); - testSubject.Reset(); + return issue; + } - eventMock.Verify(x => x(testSubject, EventArgs.Empty), Times.Exactly(1)); - } + private void InitializeStoreWithIssue(params SonarQubeIssue[] expectedIssues) => testSubject.AddIssues(expectedIssues, false); - private static ServerIssuesStore CreateTestSubject(ILogger logger = null) + private void VerifyExpectedIssues(params SonarQubeIssue[] expectedIssues) + { + var result = testSubject.Get().ToList(); + result.Should().HaveCount(expectedIssues.Length); + foreach (var expectedIssue in expectedIssues) { - logger ??= new TestLogger(logToConsole: true); - return new ServerIssuesStore(logger); + result.Should().Contain(expectedIssue); } + } - private static SonarQubeIssue CreateIssue(string key, bool isResolved = false) - { - var issue = new SonarQubeIssue(key, "", "", "", "", "", isResolved, SonarQubeIssueSeverity.Info, DateTimeOffset.MinValue, DateTimeOffset.MinValue, null, null, null); + private void VerifyNoIssues() + { + var result = testSubject.Get(); + result.Should().BeEmpty(); + } - return issue; - } + private void VerifyNotRaisedServerIssueChanged(EventHandler eventMock) => eventMock.DidNotReceive().Invoke(testSubject, Arg.Any()); - private static ServerIssuesStore InitializeStoreWithOneIssue() - { - var testSubject = CreateTestSubject(); - testSubject.AddIssues(new List() { CreateIssue("issue1", true) }, clearAllExistingIssues: false); - return testSubject; - } - } + private void VerifyRaisedServerIssueChanged(EventHandler eventMock) => eventMock.Received(1).Invoke(testSubject, Arg.Is(x => x == EventArgs.Empty)); } diff --git a/src/ConnectedMode.UnitTests/Suppressions/SuppressionIssueStoreUpdaterTests.cs b/src/ConnectedMode.UnitTests/Suppressions/SuppressionIssueStoreUpdaterTests.cs index 7bcbe9547..05b310e05 100644 --- a/src/ConnectedMode.UnitTests/Suppressions/SuppressionIssueStoreUpdaterTests.cs +++ b/src/ConnectedMode.UnitTests/Suppressions/SuppressionIssueStoreUpdaterTests.cs @@ -18,10 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; using Microsoft.VisualStudio.Threading; using SonarLint.VisualStudio.ConnectedMode.Helpers; using SonarLint.VisualStudio.ConnectedMode.Suppressions; @@ -31,432 +27,354 @@ using SonarQube.Client; using SonarQube.Client.Models; -namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Suppressions +namespace SonarLint.VisualStudio.ConnectedMode.UnitTests.Suppressions; + +[TestClass] +public class SuppressionIssueStoreUpdaterTests { - [TestClass] - public class SuppressionIssueStoreUpdaterTests + private ICancellableActionRunner actionRunner; + private TestLogger logger; + private IServerQueryInfoProvider queryInfo; + private ISonarQubeService server; + private SuppressionIssueStoreUpdater testSubject; + private IThreadHandling threadHandling; + private IServerIssuesStoreWriter writer; + + [TestInitialize] + public void TestInitialize() { - [TestMethod] - public void MefCtor_CheckIsExported() - { - MefTestHelpers.CheckTypeCanBeImported( - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport(), - MefTestHelpers.CreateExport()); - } - - [TestMethod] - [DataRow(null, null)] - [DataRow(null, "branch")] - [DataRow("projectKey", null)] - public async Task UpdateAll_MissingServerQueryInfo_ResetsStore(string projectKey, string branchName) - { - // Server query info is not available -> give up - var queryInfo = CreateQueryInfoProvider(projectKey, branchName); - var server = new Mock(); - var writer = new Mock(); - - var testSubject = CreateTestSubject(queryInfo.Object, server.Object, writer.Object); - - await testSubject.UpdateAllServerSuppressionsAsync(); - - queryInfo.Verify(x => x.GetProjectKeyAndBranchAsync(It.IsAny()), Times.Once()); - server.Invocations.Should().HaveCount(0); - writer.Verify(x => x.Reset(), Times.Once); - writer.Invocations.Should().HaveCount(1); - } - - [TestMethod] - public async Task UpdateAll_HasServerQueryInfo_ServerQueriedAndStoreUpdated() - { - // Happy path - fetch and update - var queryInfo = CreateQueryInfoProvider("project", "branch"); - - var issue = CreateIssue("issue1"); - var server = CreateSonarQubeService("project", "branch", null, issue); - - var writer = new Mock(); - - var testSubject = CreateTestSubject(queryInfo.Object, server.Object, writer.Object); - - await testSubject.UpdateAllServerSuppressionsAsync(); - - queryInfo.Verify(x => x.GetProjectKeyAndBranchAsync(It.IsAny()), Times.Once()); - server.Verify(x => x.GetSuppressedIssuesAsync("project", "branch", null, It.IsAny()), Times.Once()); - writer.Verify(x => x.AddIssues(new[] { issue }, true), Times.Once); - - server.Invocations.Should().HaveCount(1); - writer.Invocations.Should().HaveCount(1); - } - - [TestMethod] - public async Task UpdateAll_RunOnBackgroundThreadInActionRunner() - { - var callSequence = new List(); - - var queryInfo = new Mock(); - var threadHandling = new Mock(); - var actionRunner = new Mock(); - - queryInfo.Setup(x => x.GetProjectKeyAndBranchAsync(It.IsAny())) - .Callback(x => callSequence.Add("GetProjectKeyAndBranchAsync")); - - threadHandling.Setup(x => x.RunOnBackgroundThread(It.IsAny>>())) - .Returns((Func> action) => - { - callSequence.Add("RunOnBackgroundThread"); - return action(); - }); - - actionRunner.Setup(x => x.RunAsync(It.IsAny>())) - .Returns((Func action) => - { - callSequence.Add("RunAction"); - return action(CancellationToken.None); - }); - - var testSubject = CreateTestSubject(queryInfo.Object, - threadHandling: threadHandling.Object, - actionRunner:actionRunner.Object); - - await testSubject.UpdateAllServerSuppressionsAsync(); - - queryInfo.Invocations.Should().HaveCount(1); - threadHandling.Invocations.Should().HaveCount(1); + writer = Substitute.For(); + server = Substitute.For(); + queryInfo = Substitute.For(); + logger = new TestLogger(true); + threadHandling = new NoOpThreadHandler(); + actionRunner = new SynchronizedCancellableActionRunner(logger); + testSubject = CreateTestSubject(actionRunner, threadHandling); + } - callSequence.Should().ContainInOrder("RunOnBackgroundThread", "RunAction", "GetProjectKeyAndBranchAsync"); - } + [TestMethod] + public void MefCtor_CheckIsExported() => + MefTestHelpers.CheckTypeCanBeImported( + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport(), + MefTestHelpers.CreateExport()); + + [TestMethod] + [DataRow(null, null)] + [DataRow(null, "branch")] + [DataRow("projectKey", null)] + public async Task UpdateAll_MissingServerQueryInfo_ResetsStore(string projectKey, string branchName) + { + // Server query info is not available -> give up + MockQueryInfoProvider(projectKey, branchName); - [TestMethod] - public void UpdateAll_CriticalExpression_NotHandled() - { - var queryInfo = new Mock(); - queryInfo.Setup(x => x.GetProjectKeyAndBranchAsync(It.IsAny())) - .Throws(new StackOverflowException("thrown in a test")); + await testSubject.UpdateAllServerSuppressionsAsync(); - var logger = new TestLogger(logToConsole: true); + await queryInfo.Received(1).GetProjectKeyAndBranchAsync(Arg.Any()); + server.ReceivedCalls().Should().HaveCount(0); + writer.Received(1).Reset(); + writer.ReceivedCalls().Should().HaveCount(1); + } - var testSubject = CreateTestSubject(queryInfo.Object, logger: logger); + [TestMethod] + public async Task UpdateAll_HasServerQueryInfo_ServerQueriedAndStoreUpdated() + { + // Happy path - fetch and update + MockQueryInfoProvider("project", "branch"); + var issue = CreateIssue("issue1"); + MockSonarQubeService("project", "branch", null, issue); + + await testSubject.UpdateAllServerSuppressionsAsync(); + + await queryInfo.Received(1).GetProjectKeyAndBranchAsync(Arg.Any()); + await server.Received(1).GetSuppressedIssuesAsync("project", "branch", null, Arg.Any()); + writer.Received(1).AddIssues(Arg.Is>(x => VerifySonarQubeIssues(x, new[] { issue })), true); + server.ReceivedCalls().Should().HaveCount(1); + writer.ReceivedCalls().Should().HaveCount(1); + } - Func operation = testSubject.UpdateAllServerSuppressionsAsync; - operation.Should().Throw().And.Message.Should().Be("thrown in a test"); + [TestMethod] + public async Task UpdateAll_RunOnBackgroundThreadInActionRunner() + { + var mockedActionRunner = CreateMockedActionRunner(); + var mockedThreadHandling = CreateMockedThreadHandling(); + var mockedTestSubject = CreateTestSubject(mockedActionRunner, mockedThreadHandling); - logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); - } + await mockedTestSubject.UpdateAllServerSuppressionsAsync(); - [TestMethod] - public void UpdateAll_NonCriticalExpression_IsSuppressed() + Received.InOrder(() => { - var queryInfo = new Mock(); - queryInfo.Setup(x => x.GetProjectKeyAndBranchAsync(It.IsAny())) - .Throws(new InvalidOperationException("thrown in a test")); - - var logger = new TestLogger(logToConsole: true); + mockedThreadHandling.RunOnBackgroundThread(Arg.Any>>()); + mockedActionRunner.RunAsync(Arg.Any>()); + queryInfo.GetProjectKeyAndBranchAsync(Arg.Any()); + }); + queryInfo.ReceivedCalls().Should().HaveCount(1); // no other calls + } - var testSubject = CreateTestSubject(queryInfo.Object, logger: logger); + [TestMethod] + public void UpdateAll_CriticalExpression_NotHandled() + { + queryInfo.When(x => x.GetProjectKeyAndBranchAsync(Arg.Any())).Throw(new StackOverflowException("thrown in a test")); - Func operation = testSubject.UpdateAllServerSuppressionsAsync; - operation.Should().NotThrow(); + var operation = testSubject.UpdateAllServerSuppressionsAsync; - logger.AssertPartialOutputStringExists("thrown in a test"); - } + operation.Should().Throw().And.Message.Should().Be("thrown in a test"); + logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); + } - [TestMethod] - public void UpdateAll_OperationCancelledException_CancellationMessageLogged() - { - var queryInfo = new Mock(); - queryInfo.Setup(x => x.GetProjectKeyAndBranchAsync(It.IsAny())) - .Throws(new OperationCanceledException("thrown in a test")); + [TestMethod] + public void UpdateAll_NonCriticalExpression_IsSuppressed() + { + queryInfo.When(x => x.GetProjectKeyAndBranchAsync(Arg.Any())).Throw(new InvalidOperationException("thrown in a test")); - var logger = new TestLogger(logToConsole: true); + var operation = testSubject.UpdateAllServerSuppressionsAsync; - var testSubject = CreateTestSubject(queryInfo.Object, logger: logger); + operation.Should().NotThrow(); + logger.AssertPartialOutputStringExists("thrown in a test"); + } - Func operation = testSubject.UpdateAllServerSuppressionsAsync; - operation.Should().NotThrow(); + [TestMethod] + public void UpdateAll_OperationCancelledException_CancellationMessageLogged() + { + queryInfo.When(x => x.GetProjectKeyAndBranchAsync(Arg.Any())).Throw(new OperationCanceledException("thrown in a test")); - logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); - logger.AssertOutputStringExists(Resources.Suppressions_FetchOperationCancelled); - } + var operation = testSubject.UpdateAllServerSuppressionsAsync; - [TestMethod] - [Ignore] // Flaky. See https://github.com/SonarSource/sonarlint-visualstudio/issues/4307 - public async Task UpdateAll_CallInProgress_CallIsCancelled() - { - var testTimeout = GetThreadedTestTimout(); + operation.Should().NotThrow(); + logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); + logger.AssertOutputStringExists(Resources.Suppressions_FetchOperationCancelled); + } - ManualResetEvent signalToBlockFirstCall = new ManualResetEvent(false); - ManualResetEvent signalToBlockSecondCall = new ManualResetEvent(false); + [TestMethod] + [Ignore] // Flaky. See https://github.com/SonarSource/sonarlint-visualstudio/issues/4307 + public async Task UpdateAll_CallInProgress_CallIsCancelled() + { + var testTimeout = GetThreadedTestTimeout(); - bool isFirstCall = true; + var signalToBlockFirstCall = new ManualResetEvent(false); + var signalToBlockSecondCall = new ManualResetEvent(false); - var server = new Mock(); - var writer = new Mock(); + var isFirstCall = true; - var queryInfo = new Mock(); - queryInfo.Setup(x => x.GetProjectKeyAndBranchAsync(It.IsAny())) - .ReturnsAsync(("projectKey", "branch")) - .Callback((token) => + queryInfo.GetProjectKeyAndBranchAsync(Arg.Any()) + .Returns(("projectKey", "branch")); + queryInfo.When(x => x.GetProjectKeyAndBranchAsync(Arg.Any())) + .Do(x => + { + if (isFirstCall) { - if (isFirstCall) - { - Log("[1] In first call"); - isFirstCall = false; - - Log("[1] Unblocking second call..."); - signalToBlockSecondCall.Set(); - - Log("[1] Waiting to be unblocked..."); - signalToBlockFirstCall.WaitOne(testTimeout); - - Log("[1] First call finished"); - } - else - { - Log("[2] In second call"); + Log("[1] In first call"); + isFirstCall = false; - Log("[2] Unblocking the first call..."); - signalToBlockFirstCall.Set(); - Log("[2] Second call finished"); - } - }); + Log("[1] Unblocking second call..."); + signalToBlockSecondCall.Set(); - var logger = new TestLogger(logToConsole: true); + Log("[1] Waiting to be unblocked..."); + signalToBlockFirstCall.WaitOne(testTimeout); - var testSubject = CreateTestSubject(queryInfo.Object, - server.Object, - writer.Object, - logger:logger, - // Note: need a real thread-handling implementation here as the test - // needs multiple threads. - threadHandling: ThreadHandling.Instance); - - // 1. First call - don't wait for it to finish, since it should be blocked - testSubject.UpdateAllServerSuppressionsAsync().Forget(); - - Log("[main test] Waiting to be unblocked..."); - signalToBlockSecondCall.WaitOne(testTimeout) - .Should().BeTrue(); // not expecting the test to timeout + Log("[1] First call finished"); + } + else + { + Log("[2] In second call"); - // 2. Second call - "await" means we're waiting for it to run to completion - await testSubject.UpdateAllServerSuppressionsAsync(); + Log("[2] Unblocking the first call..."); + signalToBlockFirstCall.Set(); + Log("[2] Second call finished"); + } + }); - queryInfo.Invocations.Should().HaveCount(2); - var call1Token = (CancellationToken)queryInfo.Invocations[0].Arguments[0]; - var call2Token = (CancellationToken)queryInfo.Invocations[1].Arguments[0]; + var mockedTestSubject = CreateTestSubject( + // Note: need a real thread-handling implementation here as the test + // needs multiple threads. + actionRunner, ThreadHandling.Instance); - call1Token.IsCancellationRequested.Should().BeTrue(); - call2Token.IsCancellationRequested.Should().BeFalse(); + // 1. First call - don't wait for it to finish, since it should be blocked + mockedTestSubject.UpdateAllServerSuppressionsAsync().Forget(); - logger.AssertOutputStringExists(Resources.Suppressions_FetchOperationCancelled); + Log("[main test] Waiting to be unblocked..."); + signalToBlockSecondCall.WaitOne(testTimeout) + .Should().BeTrue(); // not expecting the test to timeout - // If cancellation worked then we should only have made one call to each - // of the SonarQubeService and store writer - server.Invocations.Should().HaveCount(1); - writer.Invocations.Should().HaveCount(1); + // 2. Second call - "await" means we're waiting for it to run to completion + await mockedTestSubject.UpdateAllServerSuppressionsAsync(); - void Log(string message) - { - Console.WriteLine($"[Thread {System.Threading.Thread.CurrentThread.ManagedThreadId}] {message}"); - } - } - - [TestMethod] - [DataRow(true)] - [DataRow(false)] - public async Task UpdateSuppressedIssues_EmptyIssues_NoChangesToTheStoreAndNoServerCalls(bool isResolved) - { - var queryInfo = CreateQueryInfoProvider("proj1", "branch1"); - var storeWriter = new Mock(); - var server = new Mock(); + queryInfo.ReceivedCalls().Should().HaveCount(2); + var call1Token = (CancellationToken)queryInfo.ReceivedCalls().ToList()[0].GetArguments()[0]; + var call2Token = (CancellationToken)queryInfo.ReceivedCalls().ToList()[1].GetArguments()[0]; - var testSubject = CreateTestSubject(queryInfo.Object, server.Object, storeWriter.Object); + call1Token.IsCancellationRequested.Should().BeTrue(); + call2Token.IsCancellationRequested.Should().BeFalse(); - await testSubject.UpdateSuppressedIssuesAsync(isResolved, Array.Empty(), CancellationToken.None); + logger.AssertOutputStringExists(Resources.Suppressions_FetchOperationCancelled); - queryInfo.Invocations.Count.Should().Be(0); - storeWriter.Invocations.Count.Should().Be(0); - server.Invocations.Count.Should().Be(0); - } + // If cancellation worked then we should only have made one call to each + // of the SonarQubeService and store writer + server.ReceivedCalls().Should().HaveCount(1); + writer.ReceivedCalls().Should().HaveCount(1); - [TestMethod] - [DataRow(true)] - [DataRow(false)] - public async Task UpdateSuppressedIssues_AllIssuesAreFoundInStore_NoServerCalls(bool isResolved) - { - var queryInfo = CreateQueryInfoProvider("proj1", "branch1"); - var storeWriter = CreateIssuesStore(CreateIssue("issue1"), CreateIssue("issue2")); - var server = new Mock(); + static void Log(string message) => Console.WriteLine($"[Thread {Thread.CurrentThread.ManagedThreadId}] {message}"); + } - var testSubject = CreateTestSubject(queryInfo.Object, server.Object, storeWriter.Object); + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task UpdateSuppressedIssues_EmptyIssues_NoChangesToTheStoreAndNoServerCalls(bool isResolved) + { + MockQueryInfoProvider("proj1", "branch1"); - await testSubject.UpdateSuppressedIssuesAsync(isResolved, new[]{"issue1", "issue2"}, CancellationToken.None); + await testSubject.UpdateSuppressedIssuesAsync(isResolved, [], CancellationToken.None); - storeWriter.Verify(x=> x.Get(), Times.Once); - storeWriter.Verify(x=> x.UpdateIssues(isResolved, new[] { "issue1", "issue2" }), Times.Once); - storeWriter.VerifyNoOtherCalls(); + queryInfo.ReceivedCalls().Count().Should().Be(0); + writer.ReceivedCalls().Count().Should().Be(0); + server.ReceivedCalls().Count().Should().Be(0); + } - server.Invocations.Count.Should().Be(0); - queryInfo.Invocations.Count.Should().Be(0); - } + [TestMethod] + [DataRow(true)] + [DataRow(false)] + public async Task UpdateSuppressedIssues_AllIssuesAreFoundInStore_NoServerCalls(bool isResolved) + { + MockQueryInfoProvider("proj1", "branch1"); + MockIssuesStore(CreateIssue("issue1"), CreateIssue("issue2")); - [TestMethod] - public async Task UpdateSuppressedIssues_IssuesAreNotFoundInStore_IssuesAreNotSuppressed_NoServerCalls() - { - var queryInfo = CreateQueryInfoProvider("proj1", "branch1"); - var storeWriter = CreateIssuesStore(CreateIssue("issue1"), CreateIssue("issue3")); - var server = new Mock(); + await testSubject.UpdateSuppressedIssuesAsync(isResolved, ["issue1", "issue2"], CancellationToken.None); - var testSubject = CreateTestSubject(queryInfo.Object, server.Object, storeWriter.Object); + writer.Received(1).Get(); + writer.Received(1).UpdateIssues(isResolved, Arg.Is>(actual => VerifyExistingIssues(actual, new[] { "issue1", "issue2" }))); + writer.ReceivedCalls().Should().HaveCount(2); // no other calls + server.ReceivedCalls().Count().Should().Be(0); + queryInfo.ReceivedCalls().Count().Should().Be(0); + } - await testSubject.UpdateSuppressedIssuesAsync(false, new[] { "issue1", "issue2", "issue3" }, CancellationToken.None); + [TestMethod] + public async Task UpdateSuppressedIssues_IssuesAreNotFoundInStore_IssuesAreNotSuppressed_NoServerCalls() + { + MockQueryInfoProvider("proj1", "branch1"); + MockIssuesStore(CreateIssue("issue1"), CreateIssue("issue3")); - storeWriter.Verify(x => x.Get(), Times.Once); - storeWriter.Verify(x => x.UpdateIssues(false, new[] { "issue1", "issue2", "issue3" }), Times.Once); - storeWriter.VerifyNoOtherCalls(); + await testSubject.UpdateSuppressedIssuesAsync(false, ["issue1", "issue2", "issue3"], CancellationToken.None); - // the missing issue is not suppressed, so we will not fetch it - server.Invocations.Count.Should().Be(0); - queryInfo.Invocations.Count.Should().Be(0); - } + writer.Received(1).Get(); + writer.Received(1).UpdateIssues(false, Arg.Is>(x => VerifyExistingIssues(x, new[] { "issue1", "issue2", "issue3" }))); + writer.ReceivedCalls().Should().HaveCount(2); + // the missing issue is not suppressed, so we will not fetch it + server.ReceivedCalls().Count().Should().Be(0); + queryInfo.ReceivedCalls().Count().Should().Be(0); + } - [TestMethod] - public async Task UpdateSuppressedIssues_IssuesAreNotFoundInStore_IssuesAreSuppressed_IssuesFetched() - { - var queryInfo = CreateQueryInfoProvider("proj1", "branch1"); - var storeWriter = CreateIssuesStore(CreateIssue("issue1")); - var expectedFetchedIssues = new[] {CreateIssue("issue2"), CreateIssue("issue3")}; - - var server = CreateSonarQubeService( - "proj1", - "branch1", - new[] {"issue2", "issue3"}, - expectedFetchedIssues); - - var testSubject = CreateTestSubject(queryInfo.Object, server.Object, storeWriter.Object); - - await testSubject.UpdateSuppressedIssuesAsync(true, new[] { "issue1", "issue2", "issue3" }, CancellationToken.None); - - // the missing issues are suppressed, so we need to fetch them and add them to the store - server.Verify(x => x.GetSuppressedIssuesAsync( - "proj1", - "branch1", - new[] { "issue2", "issue3" }, - CancellationToken.None), - Times.Once); - - storeWriter.Verify(x => x.Get(), Times.Once); - storeWriter.Verify(x => x.AddIssues(expectedFetchedIssues, false), Times.Once); - storeWriter.Verify(x => x.UpdateIssues(true, new[] { "issue1", "issue2", "issue3" }), Times.Once); - storeWriter.VerifyNoOtherCalls(); - } - - [TestMethod] - public void UpdateSuppressedIssues_CriticalExpression_NotHandled() - { - var storeWriter = new Mock(); - storeWriter.Setup(x => x.Get()) - .Throws(new StackOverflowException("thrown in a test")); + [TestMethod] + public async Task UpdateSuppressedIssues_IssuesAreNotFoundInStore_IssuesAreSuppressed_IssuesFetched() + { + MockQueryInfoProvider("proj1", "branch1"); + MockIssuesStore(CreateIssue("issue1")); + var expectedFetchedIssues = new[] { CreateIssue("issue2"), CreateIssue("issue3") }; + MockSonarQubeService( + "proj1", + "branch1", + ["issue2", "issue3"], + expectedFetchedIssues); + + await testSubject.UpdateSuppressedIssuesAsync(true, ["issue1", "issue2", "issue3"], CancellationToken.None); + + // the missing issues are suppressed, so we need to fetch them and add them to the store + await server.Received(1).GetSuppressedIssuesAsync( + "proj1", + "branch1", + Arg.Is(x => VerifyExistingIssues(x, new[] { "issue2", "issue3" })), + CancellationToken.None); + writer.Received(1).Get(); + writer.Received(1).AddIssues(Arg.Is>(x => VerifySonarQubeIssues(x, expectedFetchedIssues)), false); + writer.Received(1).UpdateIssues(true, Arg.Is>(x => VerifyExistingIssues(x, new[] { "issue1", "issue2", "issue3" }))); + writer.ReceivedCalls().Should().HaveCount(3); + } - var logger = new TestLogger(logToConsole: true); + [TestMethod] + public void UpdateSuppressedIssues_CriticalExpression_NotHandled() + { + writer.When(x => x.Get()).Throw(new StackOverflowException("thrown in a test")); - var testSubject = CreateTestSubject(writer: storeWriter.Object, logger: logger); + var operation = () => testSubject.UpdateSuppressedIssuesAsync(true, ["issue1"], CancellationToken.None); - Func operation = () => testSubject.UpdateSuppressedIssuesAsync(true, new[]{"issue1"}, CancellationToken.None); - operation.Should().Throw().And.Message.Should().Be("thrown in a test"); + operation.Should().Throw().And.Message.Should().Be("thrown in a test"); + logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); + } - logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); - } + [TestMethod] + public void UpdateSuppressedIssues_NonCriticalExpression_IsSuppressed() + { + writer.When(x => x.Get()).Throw(new InvalidOperationException("thrown in a test")); - [TestMethod] - public void UpdateSuppressedIssues_NonCriticalExpression_IsSuppressed() - { - var storeWriter = new Mock(); - storeWriter.Setup(x => x.Get()) - .Throws(new InvalidOperationException("thrown in a test")); + var operation = () => testSubject.UpdateSuppressedIssuesAsync(true, ["issue1"], CancellationToken.None); - var logger = new TestLogger(logToConsole: true); + operation.Should().NotThrow(); + logger.AssertPartialOutputStringExists("thrown in a test"); + } - var testSubject = CreateTestSubject(writer: storeWriter.Object, logger: logger); + [TestMethod] + public void UpdateSuppressedIssues_OperationCancelledException_CancellationMessageLogged() + { + writer.When(x => x.Get()).Throw(new OperationCanceledException("thrown in a test")); - Func operation = () => testSubject.UpdateSuppressedIssuesAsync(true, new[] { "issue1" }, CancellationToken.None); - operation.Should().NotThrow(); + var operation = () => testSubject.UpdateSuppressedIssuesAsync(true, ["issue1"], CancellationToken.None); - logger.AssertPartialOutputStringExists("thrown in a test"); - } + operation.Should().NotThrow(); + logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); + logger.AssertOutputStringExists(Resources.Suppressions_UpdateOperationCancelled); + } - [TestMethod] - public void UpdateSuppressedIssues_OperationCancelledException_CancellationMessageLogged() - { - var storeWriter = new Mock(); - storeWriter.Setup(x => x.Get()) - .Throws(new OperationCanceledException("thrown in a test")); + private SuppressionIssueStoreUpdater CreateTestSubject(ICancellableActionRunner mockedActionRunner, IThreadHandling mockedThreadHandling) => + new(server, queryInfo, writer, mockedActionRunner, logger, mockedThreadHandling); - var logger = new TestLogger(logToConsole: true); + private void MockQueryInfoProvider(string projectKey, string branchName) => queryInfo.GetProjectKeyAndBranchAsync(Arg.Any()).Returns((projectKey, branchName)); - var testSubject = CreateTestSubject(writer: storeWriter.Object, logger: logger); + private void MockSonarQubeService( + string projectKey, + string branchName, + string[] issueKeys, + params SonarQubeIssue[] issuesToReturn) => + server.GetSuppressedIssuesAsync(projectKey, branchName, Arg.Is(x => issueKeys == null || x.SequenceEqual(issueKeys)), Arg.Any()).Returns(issuesToReturn); - Func operation = () => testSubject.UpdateSuppressedIssuesAsync(true, new[] { "issue1" }, CancellationToken.None); - operation.Should().NotThrow(); + private static SonarQubeIssue CreateIssue(string issueKey) => + new(issueKey, + null, null, null, null, null, true, + SonarQubeIssueSeverity.Info, DateTimeOffset.MinValue, + DateTimeOffset.MaxValue, null, null); - logger.AssertPartialOutputStringDoesNotExist("thrown in a test"); - logger.AssertOutputStringExists(Resources.Suppressions_UpdateOperationCancelled); - } + private static TimeSpan GetThreadedTestTimeout() + // This test uses a number of manual signals to control the order of execution. + // We want a longer timeout when debugging. + => + Debugger.IsAttached ? TimeSpan.FromMinutes(2) : TimeSpan.FromMilliseconds(200); - private static SuppressionIssueStoreUpdater CreateTestSubject(IServerQueryInfoProvider queryInfo = null, - ISonarQubeService server = null, - IServerIssuesStoreWriter writer = null, - ILogger logger = null, - ICancellableActionRunner actionRunner = null, - IThreadHandling threadHandling = null) - { - writer ??= Mock.Of(); - server ??= Mock.Of(); - queryInfo ??= Mock.Of(); - logger ??= new TestLogger(logToConsole: true); - threadHandling ??= new NoOpThreadHandler(); - actionRunner ??= new SynchronizedCancellableActionRunner(logger); + private void MockIssuesStore(params SonarQubeIssue[] issuesInStore) => writer.Get().Returns(issuesInStore); - return new SuppressionIssueStoreUpdater(server, queryInfo, writer, actionRunner, logger, threadHandling); - } + private static bool VerifyExistingIssues(IEnumerable actualIssues, IEnumerable expectedIssues) => actualIssues.SequenceEqual(expectedIssues); - private static Mock CreateQueryInfoProvider(string projectKey, string branchName) - { - var mock = new Mock(); - mock.Setup(x => x.GetProjectKeyAndBranchAsync(It.IsAny())).ReturnsAsync((projectKey, branchName)); - return mock; - } + private static bool VerifySonarQubeIssues(IEnumerable actualIssues, SonarQubeIssue[] expectedIssues) => actualIssues.SequenceEqual(expectedIssues); - private static Mock CreateSonarQubeService(string projectKey, string branchName, string[] issueKeys, params SonarQubeIssue[] issuesToReturn) - { - var mock = new Mock(); - mock.Setup(x => x.GetSuppressedIssuesAsync(projectKey, branchName, issueKeys, It.IsAny())) - .ReturnsAsync(issuesToReturn); - - return mock; - } - - private static SonarQubeIssue CreateIssue(string issueKey) => - new SonarQubeIssue(issueKey, - null, null, null, null, null, true, - SonarQubeIssueSeverity.Info, DateTimeOffset.MinValue, - DateTimeOffset.MaxValue, null, null); - - private static TimeSpan GetThreadedTestTimout() - // This test uses a number of manual signals to control the order of execution. - // We want a longer timeout when debugging. - => System.Diagnostics.Debugger.IsAttached ? - TimeSpan.FromMinutes(2) : TimeSpan.FromMilliseconds(200); - - private static Mock CreateIssuesStore(params SonarQubeIssue[] issuesInStore) + private static IThreadHandling CreateMockedThreadHandling() + { + var mockedThreadHandling = Substitute.For(); + mockedThreadHandling.When(x => x.RunOnBackgroundThread(Arg.Any>>())).Do(x => { - var store = new Mock(); - - store.Setup(x => x.Get()).Returns(issuesInStore); + var func = x.Arg>>(); + func(); + }); + return mockedThreadHandling; + } - return store; - } + private static ICancellableActionRunner CreateMockedActionRunner() + { + var mockedActionRunner = Substitute.For(); + mockedActionRunner.When(x => x.RunAsync(Arg.Any>())) + .Do(callInfo => + { + var func = callInfo.Arg>(); + func(new CancellationToken()); + }); + return mockedActionRunner; } } diff --git a/src/ConnectedMode/Suppressions/IServerIssuesStore.cs b/src/ConnectedMode/Suppressions/IServerIssuesStore.cs index 14d204226..c782e251a 100644 --- a/src/ConnectedMode/Suppressions/IServerIssuesStore.cs +++ b/src/ConnectedMode/Suppressions/IServerIssuesStore.cs @@ -18,8 +18,6 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; -using System.Collections.Generic; using SonarQube.Client.Models; namespace SonarLint.VisualStudio.ConnectedMode.Suppressions diff --git a/src/ConnectedMode/Suppressions/SuppressionIssueStoreUpdater.cs b/src/ConnectedMode/Suppressions/SuppressionIssueStoreUpdater.cs index 9e3084833..6742e4c0b 100644 --- a/src/ConnectedMode/Suppressions/SuppressionIssueStoreUpdater.cs +++ b/src/ConnectedMode/Suppressions/SuppressionIssueStoreUpdater.cs @@ -18,13 +18,9 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -using System; using System.ComponentModel.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.ConnectedMode.Helpers; +using SonarLint.VisualStudio.Core; using SonarLint.VisualStudio.Infrastructure.VS; using SonarQube.Client; @@ -58,7 +54,8 @@ internal sealed class SuppressionIssueStoreUpdater : ISuppressionIssueStoreUpdat private readonly IThreadHandling threadHandling; [ImportingConstructor] - public SuppressionIssueStoreUpdater(ISonarQubeService server, + public SuppressionIssueStoreUpdater( + ISonarQubeService server, IServerQueryInfoProvider serverQueryInfoProvider, IServerIssuesStoreWriter storeWriter, ICancellableActionRunner actionRunner, @@ -67,7 +64,8 @@ public SuppressionIssueStoreUpdater(ISonarQubeService server, { } - internal /* for testing */ SuppressionIssueStoreUpdater(ISonarQubeService server, + internal /* for testing */ SuppressionIssueStoreUpdater( + ISonarQubeService server, IServerQueryInfoProvider serverQueryInfoProvider, IServerIssuesStoreWriter storeWriter, ICancellableActionRunner actionRunner,