Skip to content

Commit

Permalink
Warn User if issue is not found on Server (#5091)
Browse files Browse the repository at this point in the history
Fixes #5086
  • Loading branch information
ugras-ergun-sonarsource authored Dec 7, 2023
1 parent 6bf7fb1 commit c58e9b4
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 45 deletions.
73 changes: 39 additions & 34 deletions src/Integration.Vsix.UnitTests/Analysis/MuteIssueCommandTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
using System.ComponentModel.Design;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using FluentAssertions;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.TestTools.UnitTesting;
Expand All @@ -47,26 +48,26 @@ public class MuteIssueCommandTests

private static readonly BindingConfiguration ConnectedModeBinding =
new BindingConfiguration(default, SonarLintMode.Connected, default);

[TestMethod]
public void CommandRegistration()
{
var testSubject = CreateTestSubject(out _, out _, out _, out _, out _, out _);
var testSubject = CreateTestSubject(out _, out _, out _, out _, out _, out _, out _);

testSubject.CommandID.ID.Should().Be(MuteIssueCommand.CommandId);
testSubject.CommandID.Guid.Should().Be(MuteIssueCommand.CommandSet);
}

[TestMethod]
public void QueryStatus_NotSonarIssue_Invisible()
{
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _);
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _, out _);
var rule = It.IsAny<SonarCompositeRuleId>();
errorListHelperMock.Setup(x => x.TryGetRuleIdFromSelectedRow(out rule)).Returns(false);
activeSolutionBoundTrackerMock
.SetupGet(x => x.CurrentConfiguration)
.Returns(ConnectedModeBinding);

ThreadHelper.SetCurrentThreadAsUIThread();
var oleStatus = testSubject.OleStatus;

Expand All @@ -75,43 +76,43 @@ public void QueryStatus_NotSonarIssue_Invisible()
testSubject.Enabled.Should().BeFalse();
}

[TestMethod]
[TestMethod]
public void QueryStatus_NotSupportedIssue_Invisible()
{
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _);
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _, out _);
SonarCompositeRuleId.TryParse("csharpsquid:S333", out SonarCompositeRuleId ruleId);
errorListHelperMock.Setup(x => x.TryGetRuleIdFromSelectedRow(out ruleId)).Returns(true);
activeSolutionBoundTrackerMock
.SetupGet(x => x.CurrentConfiguration)
.Returns(ConnectedModeBinding);

ThreadHelper.SetCurrentThreadAsUIThread();
var oleStatus = testSubject.OleStatus;

oleStatus.Should().Be(InvisibleAndDisabled);
testSubject.Visible.Should().BeFalse();
testSubject.Enabled.Should().BeFalse();
}
[TestMethod]

[TestMethod]
public void QueryStatus_NotInConnectedMode_VisibleButDisabled()
{
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _);
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _, out _);
SonarCompositeRuleId.TryParse("javascript:S333", out SonarCompositeRuleId ruleId);
errorListHelperMock.Setup(x => x.TryGetRuleIdFromSelectedRow(out ruleId)).Returns(true);
activeSolutionBoundTrackerMock
.SetupGet(x => x.CurrentConfiguration)
.Returns(BindingConfiguration.Standalone);

ThreadHelper.SetCurrentThreadAsUIThread();
var oleStatus = testSubject.OleStatus;

oleStatus.Should().Be(VisibleButDisabled);
testSubject.Visible.Should().BeTrue();
testSubject.Enabled.Should().BeFalse();
}
[TestMethod]

[TestMethod]
[DataRow("cpp:S111")]
[DataRow("c:S222")]
[DataRow("javascript:S333")]
Expand All @@ -120,43 +121,44 @@ public void QueryStatus_NotInConnectedMode_VisibleButDisabled()
[DataRow("css:S555")]
public void QueryStatus_ConnectedModeAndSupportedIssue_VisibleAndEnabled(string errorCode)
{
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _);
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out var activeSolutionBoundTrackerMock, out _, out _, out _);
SonarCompositeRuleId.TryParse(errorCode, out SonarCompositeRuleId ruleId);
errorListHelperMock.Setup(x => x.TryGetRuleIdFromSelectedRow(out ruleId)).Returns(true);
activeSolutionBoundTrackerMock
.SetupGet(x => x.CurrentConfiguration)
.Returns(ConnectedModeBinding);

ThreadHelper.SetCurrentThreadAsUIThread();
var oleStatus = testSubject.OleStatus;

oleStatus.Should().Be(VisibleAndEnabled);
testSubject.Visible.Should().BeTrue();
testSubject.Enabled.Should().BeTrue();
}

[TestMethod]
public void Execute_DoesNothingWhenIssueCannotBeSelected()
{
// todo roslyn issues should be checked in this case, but it's not yet implemented
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out _, out _, out _);
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out _, out _, out _, out _);
var issue = Mock.Of<IFilterableIssue>();
errorListHelperMock.Setup(x => x.TryGetIssueFromSelectedRow(out issue)).Returns(false);

testSubject.Invoke();

errorListHelperMock.Verify(x => x.TryGetIssueFromSelectedRow(out issue), Times.Once);
}

[TestMethod]
public void Execute_DoesNotFindServerIssue_TODO()
public void Execute_DoesNotFindServerIssue_ShowsMessageBox()
{
var callSequence = new MockSequence();
var testSubject = CreateTestSubject(out var errorListHelperMock,
out var serverIssueFinderMock,
out _,
out _,
out var threadHandlingMock,
out var messageBoxMock,
out _);
var issue = Mock.Of<IFilterableIssue>();
errorListHelperMock
Expand All @@ -173,12 +175,12 @@ public void Execute_DoesNotFindServerIssue_TODO()
.ReturnsAsync((SonarQubeIssue)null);

testSubject.Invoke();

threadHandlingMock.Verify(x => x.RunOnBackgroundThread(It.IsAny<Func<Task<bool>>>()), Times.Once);
serverIssueFinderMock.Verify(x => x.FindServerIssueAsync(issue, It.IsAny<CancellationToken>()), Times.Once);
// todo should signal the user that the issue was not found, not implemented yet
messageBoxMock.Verify(x => x.Show(AnalysisStrings.MuteIssue_IssueNotFoundText, AnalysisStrings.MuteIssue_IssueNotFoundCaption, MessageBoxButton.OK, MessageBoxImage.Exclamation));
}

[TestMethod]
public void Execute_MutesIssue()
{
Expand All @@ -188,6 +190,7 @@ public void Execute_MutesIssue()
out var muteIssueServiceMock,
out _,
out var threadHandlingMock,
out _,
out _);
var issue = Mock.Of<IFilterableIssue>();
var sonarQubeIssue = CreateSonarQubeIssue();
Expand All @@ -209,7 +212,7 @@ public void Execute_MutesIssue()
.Returns(Task.CompletedTask);

testSubject.Invoke();

errorListHelperMock.Verify(x => x.TryGetIssueFromSelectedRow(out issue), Times.Once);
threadHandlingMock.Verify(x => x.RunOnBackgroundThread(It.IsAny<Func<Task<bool>>>()), Times.Once);
serverIssueFinderMock.Verify(x => x.FindServerIssueAsync(issue, It.IsAny<CancellationToken>()), Times.Once);
Expand All @@ -220,55 +223,57 @@ public void Execute_MutesIssue()
public void Execute_ExceptionCaught()
{
var callSequence = new MockSequence();
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out _, out _, out var logger);
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out _, out _, out _, out var logger);
var issue = Mock.Of<IFilterableIssue>();
errorListHelperMock
.InSequence(callSequence)
.Setup(x => x.TryGetIssueFromSelectedRow(out issue))
.Throws(new Exception("exception xxx"));

Action act = () => testSubject.Invoke();

act.Should().NotThrow();
logger.AssertPartialOutputStringExists("exception xxx");
}
}

[TestMethod]
public void Execute_CriticalExceptionNotCaught()
{
var callSequence = new MockSequence();
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out _, out _, out var logger);
var testSubject = CreateTestSubject(out var errorListHelperMock, out _, out _, out _, out _, out _, out var logger);
var issue = Mock.Of<IFilterableIssue>();
errorListHelperMock
.InSequence(callSequence)
.Setup(x => x.TryGetIssueFromSelectedRow(out issue))
.Throws(new DivideByZeroException("exception xxx"));

Action act = () => testSubject.Invoke();

act.Should().ThrowExactly<DivideByZeroException>();
logger.AssertPartialOutputStringDoesNotExist("exception xxx");
}

private static SonarQubeIssue CreateSonarQubeIssue()
{
return new SonarQubeIssue("issueKey", default, default, default, default, default, default, default,default,default,default,default);
return new SonarQubeIssue("issueKey", default, default, default, default, default, default, default, default, default, default, default);
}

private MenuCommand CreateTestSubject(out Mock<IErrorListHelper> errorListHelperMock,
out Mock<IServerIssueFinder> serverIssueFinderMock,
out Mock<IMuteIssuesService> muteIssueServiceMock,
out Mock<IActiveSolutionBoundTracker> activeSolutionBoundTrackerMock,
out Mock<IThreadHandling> threadHandlingMock,
out Mock<IMessageBox> messeageBox,
out TestLogger logger)
{
var dummyMenuService = new DummyMenuCommandService();
new MuteIssueCommand(dummyMenuService,
new MuteIssueCommand(dummyMenuService,
(errorListHelperMock = new Mock<IErrorListHelper>(MockBehavior.Strict)).Object,
(serverIssueFinderMock = new Mock<IServerIssueFinder>(MockBehavior.Strict)).Object,
(muteIssueServiceMock = new Mock<IMuteIssuesService>(MockBehavior.Strict)).Object,
(activeSolutionBoundTrackerMock = new Mock<IActiveSolutionBoundTracker>(MockBehavior.Strict)).Object,
(threadHandlingMock = new Mock<IThreadHandling>()).Object,
(messeageBox = new Mock<IMessageBox>()).Object,
logger = new TestLogger());

dummyMenuService.AddedMenuCommands.Count.Should().Be(1);
Expand Down
19 changes: 19 additions & 0 deletions src/Integration.Vsix/Analysis/AnalysisStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Integration.Vsix/Analysis/AnalysisStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,10 @@
<data name="MuteIssue_ErrorCheckingCommandStatus" xml:space="preserve">
<value>Error in MuteIssueCommand.QueryStatus. Error: {0}</value>
</data>
<data name="MuteIssue_IssueNotFoundCaption" xml:space="preserve">
<value>Failure</value>
</data>
<data name="MuteIssue_IssueNotFoundText" xml:space="preserve">
<value>Could not find a matching issue on the server.</value>
</data>
</root>
24 changes: 13 additions & 11 deletions src/Integration.Vsix/Analysis/MuteIssueCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,16 @@ internal sealed class MuteIssueCommand
private readonly IActiveSolutionBoundTracker activeSolutionBoundTracker;
private readonly IThreadHandling threadHandling;
private readonly ILogger logger;
private readonly IMessageBox messageBox;

// Command set guid and command id. Must match those in DaemonCommands.vsct
public static readonly Guid CommandSet = new Guid("1F83EA11-3B07-45B3-BF39-307FD4F42194");
public const int CommandId = 0x0400;

private readonly OleMenuCommand menuItem;

public static MuteIssueCommand Instance { get; private set; }

public static async Task InitializeAsync(AsyncPackage package, ILogger logger)
{
// Switch to the main thread - the call to AddCommand in Command1's constructor requires
Expand All @@ -67,15 +68,17 @@ await package.GetMefServiceAsync<IServerIssueFinder>(),
await package.GetMefServiceAsync<IMuteIssuesService>(),
await package.GetMefServiceAsync<IActiveSolutionBoundTracker>(),
await package.GetMefServiceAsync<IThreadHandling>(),
new MessageBox(),
logger);
}
internal MuteIssueCommand(IMenuCommandService menuCommandService,

internal MuteIssueCommand(IMenuCommandService menuCommandService,
IErrorListHelper errorListHelper,
IServerIssueFinder serverIssueFinder,
IMuteIssuesService muteIssuesService,
IMuteIssuesService muteIssuesService,
IActiveSolutionBoundTracker activeSolutionBoundTracker,
IThreadHandling threadHandling,
IMessageBox messageBox,
ILogger logger)
{
if (menuCommandService == null)
Expand All @@ -89,7 +92,7 @@ internal MuteIssueCommand(IMenuCommandService menuCommandService,
this.activeSolutionBoundTracker = activeSolutionBoundTracker ?? throw new ArgumentNullException(nameof(activeSolutionBoundTracker));
this.threadHandling = threadHandling ?? throw new ArgumentNullException(nameof(threadHandling));
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));

this.messageBox = messageBox ?? throw new ArgumentNullException(nameof(messageBox));

var menuCommandID = new CommandID(CommandSet, CommandId);
menuItem = new OleMenuCommand(Execute, null, QueryStatus, menuCommandID);
Expand Down Expand Up @@ -128,29 +131,28 @@ private void Execute(object sender, EventArgs e)
{
logger.WriteLine(AnalysisStrings.MuteIssue_ErrorMutingIssue, ex.Message);
}

}

private async Task<bool> MuteIssueAsync(IFilterableIssue issue)
{
var serverIssue = await serverIssueFinder.FindServerIssueAsync(issue, CancellationToken.None);
if (serverIssue == null)
{
// todo show user message
messageBox.Show(AnalysisStrings.MuteIssue_IssueNotFoundText, AnalysisStrings.MuteIssue_IssueNotFoundCaption, System.Windows.MessageBoxButton.OK, System.Windows.MessageBoxImage.Exclamation);
return false;
}

await muteIssuesService.Mute(serverIssue.IssueKey, CancellationToken.None);
logger.WriteLine(AnalysisStrings.MuteIssue_HaveMuted, serverIssue.IssueKey);

return true;
}

// Strictly speaking we are allowing rules from known repos to be disabled,
// not "all rules for language X". However, since we are in control of the
// rules/repos that are installed in VSIX, checking the repo key is good
// enough.
private static readonly string[] SupportedRepos =
private static readonly string[] SupportedRepos =
{
SonarRuleRepoKeys.C,
SonarRuleRepoKeys.Cpp,
Expand Down

0 comments on commit c58e9b4

Please sign in to comment.