Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
using System.Windows;
using SonarLint.VisualStudio.Core;
using SonarLint.VisualStudio.Core.Analysis;
using SonarLint.VisualStudio.IssueVisualization.Security.DependencyRisks;
using SonarLint.VisualStudio.TestInfrastructure;

namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.DependencyRisks;

[TestClass]
public class DependencyRisksReportViewModelTest
{
private IDependencyRisksStore dependencyRisksStore;
private IShowDependencyRiskInBrowserHandler showDependencyRiskInBrowserHandler;
private IChangeDependencyRiskStatusHandler changeDependencyRiskStatusHandler;
private IMessageBox messageBox;
private DependencyRisksReportViewModel testSubject;

[TestInitialize]
public void Initialize()
{
dependencyRisksStore = Substitute.For<IDependencyRisksStore>();
showDependencyRiskInBrowserHandler = Substitute.For<IShowDependencyRiskInBrowserHandler>();
changeDependencyRiskStatusHandler = Substitute.For<IChangeDependencyRiskStatusHandler>();
messageBox = Substitute.For<IMessageBox>();

testSubject = new DependencyRisksReportViewModel(dependencyRisksStore, showDependencyRiskInBrowserHandler, changeDependencyRiskStatusHandler, messageBox);
}

[TestMethod]
public void MefCtor_CheckIsExported() =>
MefTestHelpers.CheckTypeCanBeImported<DependencyRisksReportViewModel, IDependencyRisksReportViewModel>(
MefTestHelpers.CreateExport<IDependencyRisksStore>(),
MefTestHelpers.CreateExport<IShowDependencyRiskInBrowserHandler>(),
MefTestHelpers.CreateExport<IChangeDependencyRiskStatusHandler>(),
MefTestHelpers.CreateExport<IMessageBox>()
);

[TestMethod]
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<DependencyRisksReportViewModel>();

[TestMethod]
public void Constructor_SubscribesToIssuesChanged() => dependencyRisksStore.Received().DependencyRisksChanged += Arg.Any<EventHandler>();

[TestMethod]
public void Dispose_UnsubscribesFromIssuesChanged()
{
testSubject.Dispose();

dependencyRisksStore.Received().DependencyRisksChanged -= Arg.Any<EventHandler>();
}

[TestMethod]
public void ShowDependencyRiskInBrowser_CallsHandler()
{
var riskId = Guid.NewGuid();
var dependencyRisk = CreateDependencyRisk(riskId);

testSubject.ShowDependencyRiskInBrowser(dependencyRisk);

showDependencyRiskInBrowserHandler.Received(1).ShowInBrowser(riskId);
}

[TestMethod]
public async Task ChangeDependencyRiskStatusAsync_CallsHandler_Success()
{
var riskId = Guid.NewGuid();
var dependencyRisk = CreateDependencyRisk(riskId);
var transition = DependencyRiskTransition.Accept;
var comment = "test comment";
changeDependencyRiskStatusHandler.ChangeStatusAsync(riskId, transition, comment).Returns(true);

await testSubject.ChangeDependencyRiskStatusAsync(dependencyRisk, transition, comment);

await changeDependencyRiskStatusHandler.Received(1).ChangeStatusAsync(riskId, transition, comment);
messageBox.DidNotReceiveWithAnyArgs().Show(Arg.Any<string>(), Arg.Any<string>(), Arg.Any<MessageBoxButton>(), Arg.Any<MessageBoxImage>());
}

[TestMethod]
public async Task ChangeDependencyRiskStatusAsync_CallsHandler_Failure_ShowsMessageBox()
{
var riskId = Guid.NewGuid();
var dependencyRisk = CreateDependencyRisk(riskId);
const DependencyRiskTransition transition = DependencyRiskTransition.Accept;
const string comment = "test comment";
changeDependencyRiskStatusHandler.ChangeStatusAsync(riskId, transition, comment).Returns(false);

await testSubject.ChangeDependencyRiskStatusAsync(dependencyRisk, transition, comment);

await changeDependencyRiskStatusHandler.Received(1).ChangeStatusAsync(riskId, transition, comment);
messageBox.Received(1).Show(Resources.DependencyRiskStatusChangeFailedTitle, Resources.DependencyRiskStatusChangeError, MessageBoxButton.OK, MessageBoxImage.Error);
}

[TestMethod]
public async Task ChangeDependencyRiskStatusAsync_NullTransition_DoesNotCallHandler_ShowsMessageBox()
{
var dependencyRisk = CreateDependencyRisk();
DependencyRiskTransition? transition = null;
const string comment = "test comment";

await testSubject.ChangeDependencyRiskStatusAsync(dependencyRisk, transition, comment);

await changeDependencyRiskStatusHandler.DidNotReceiveWithAnyArgs().ChangeStatusAsync(Arg.Any<Guid>(), Arg.Any<DependencyRiskTransition>(), Arg.Any<string>());
messageBox.Received(1).Show(Resources.DependencyRiskStatusChangeFailedTitle, Resources.DependencyRiskNullTransitionError, MessageBoxButton.OK, MessageBoxImage.Error);
}

[TestMethod]
public void GetDependencyRisksGroup_ReturnsGroup_WhenNotFixedRisksExist()
{
var risk1 = CreateDependencyRisk();
var risk2 = CreateDependencyRisk(isFixed: true);
var risk3 = CreateDependencyRisk();
dependencyRisksStore.GetAll().Returns([risk1, risk2, risk3]);

var group = testSubject.GetDependencyRisksGroup();

group.Should().NotBeNull();
group.FilteredIssues.Should().HaveCount(2);
group.FilteredIssues.Should().AllBeOfType<DependencyRiskViewModel>();
group.FilteredIssues.Cast<DependencyRiskViewModel>().Select(vm => vm.DependencyRisk).Should().Contain([risk1, risk3]);
}

[TestMethod]
public void GetDependencyRisksGroup_ReturnsNull_WhenAllRisksAreFixed()
{
var fixedRisk = CreateDependencyRisk(isFixed: true);
var fixedRisk2 = CreateDependencyRisk(isFixed: true);
dependencyRisksStore.GetAll().Returns([fixedRisk, fixedRisk2]);

var group = testSubject.GetDependencyRisksGroup();

group.Should().BeNull();
}

[TestMethod]
public void GetDependencyRisksGroup_ReturnsNull_WhenNoRisks()
{
dependencyRisksStore.GetAll().Returns([]);

var group = testSubject.GetDependencyRisksGroup();

group.Should().BeNull();
}

[TestMethod]
public void DependencyRisksChanged_RaisedOnStoreIssuesChanged()
{
var raised = false;
testSubject.DependencyRisksChanged += (_, _) => raised = true;

dependencyRisksStore.DependencyRisksChanged += Raise.Event<EventHandler>(null, null);

raised.Should().BeTrue();
}

private static IDependencyRisk CreateDependencyRisk(Guid? id = null, bool isFixed = false)
{
var risk = Substitute.For<IDependencyRisk>();
risk.Id.Returns(id ?? Guid.NewGuid());
risk.Transitions.Returns([]);
risk.Status.Returns(isFixed ? DependencyRiskStatus.Fixed : DependencyRiskStatus.Open);
return risk;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* SonarLint for Visual Studio
* Copyright (C) 2016-2025 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using SonarLint.VisualStudio.Core.Analysis;
using SonarLint.VisualStudio.IssueVisualization.Models;
using SonarLint.VisualStudio.IssueVisualization.Security.Hotspots;
using SonarLint.VisualStudio.IssueVisualization.Security.IssuesStore;
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView;
using SonarLint.VisualStudio.IssueVisualization.Security.ReportView.Hotspots;
using SonarLint.VisualStudio.TestInfrastructure;

namespace SonarLint.VisualStudio.IssueVisualization.Security.UnitTests.ReportView.Hotspots;

[TestClass]
public class HotspotsReportViewModelTest
{
private ILocalHotspotsStore localHotspotsStore;
private HotspotsReportViewModel testSubject;

[TestInitialize]
public void TestInitialize()
{
localHotspotsStore = Substitute.For<ILocalHotspotsStore>();
testSubject = new HotspotsReportViewModel(localHotspotsStore);
}

[TestMethod]
public void MefCtor_CheckIsExported() =>
MefTestHelpers.CheckTypeCanBeImported<HotspotsReportViewModel, IHotspotsReportViewModel>(
MefTestHelpers.CreateExport<ILocalHotspotsStore>());

[TestMethod]
public void MefCtor_CheckIsSingleton() => MefTestHelpers.CheckIsSingletonMefComponent<HotspotsReportViewModel>();

[TestMethod]
public void Constructor_SubscribesToIssuesChanged() => localHotspotsStore.Received().IssuesChanged += Arg.Any<EventHandler<IssuesChangedEventArgs>>();

[TestMethod]
public void Dispose_UnsubscribesFromIssuesChanged()
{
testSubject.Dispose();

localHotspotsStore.Received().IssuesChanged -= Arg.Any<EventHandler<IssuesChangedEventArgs>>();
}

[TestMethod]
public void GetHotspotsGroupViewModels_GroupsByFilePath()
{
var file1 = "file1.cs";
var file2 = "file2.cs";
MockHotspotsInStore(CreateMockedHotspot(file1), CreateMockedHotspot(file1), CreateMockedHotspot(file2));

var groups = testSubject.GetHotspotsGroupViewModels();

groups.Should().HaveCount(2);
groups.Select(g => g.Title).Should().Contain([file1, file2]);
groups.First(g => g.Title == file1).FilteredIssues.Should().HaveCount(2);
groups.First(g => g.Title == file2).FilteredIssues.Should().ContainSingle();
}

[TestMethod]
public void GetHotspotsGroupViewModels_TwoHotspotsInSameFile_CreatesOneGroupVmWithTwoIssues()
{
var path = "myFile.cs";
var hotspot1 = CreateMockedHotspot(path);
var hotspot2 = CreateMockedHotspot(path);
MockHotspotsInStore(hotspot1, hotspot2);

var groups = testSubject.GetHotspotsGroupViewModels();

groups.Should().ContainSingle();
VerifyExpectedHotspotGroupViewModel(groups[0] as GroupFileViewModel, hotspot1, hotspot2);
}

[TestMethod]
public void GetHotspotsGroupViewModels_TwoHotspotsInDifferentFiles_CreatesTwoGroupsWithOneIssueEach()
{
var hotspot1 = CreateMockedHotspot("myFile.cs");
var hotspot2 = CreateMockedHotspot("myFile.js");
MockHotspotsInStore(hotspot1, hotspot2);

var groups = testSubject.GetHotspotsGroupViewModels();

groups.Should().HaveCount(2);
VerifyExpectedHotspotGroupViewModel(groups[0] as GroupFileViewModel, hotspot1);
VerifyExpectedHotspotGroupViewModel(groups[1] as GroupFileViewModel, hotspot2);
}

[TestMethod]
public void HotspotsChanged_RaisedOnStoreIssuesChanged()
{
var raised = false;
testSubject.HotspotsChanged += (_, _) => raised = true;

localHotspotsStore.IssuesChanged += Raise.Event<EventHandler<IssuesChangedEventArgs>>(null, null);

raised.Should().BeTrue();
}

private static LocalHotspot CreateMockedHotspot(string filePath)
{
var analysisIssueVisualization = Substitute.For<IAnalysisIssueVisualization>();
var analysisIssueBase = Substitute.For<IAnalysisIssueBase>();
analysisIssueBase.PrimaryLocation.FilePath.Returns(filePath);
analysisIssueVisualization.Issue.Returns(analysisIssueBase);

return new LocalHotspot(analysisIssueVisualization, default, default);
}

private void MockHotspotsInStore(params LocalHotspot[] hotspots) => localHotspotsStore.GetAllLocalHotspots().Returns(hotspots);

private static void VerifyExpectedHotspotGroupViewModel(GroupFileViewModel groupFileVm, params LocalHotspot[] expectedHotspots)
{
groupFileVm.Should().NotBeNull();
groupFileVm.FilePath.Should().Be(expectedHotspots[0].Visualization.Issue.PrimaryLocation.FilePath);
groupFileVm.FilteredIssues.Should().HaveCount(expectedHotspots.Length);
foreach (var expectedHotspot in expectedHotspots)
{
groupFileVm.FilteredIssues.Should().ContainSingle(vm => ((HotspotViewModel)vm).LocalHotspot == expectedHotspot);
}
}
}
Loading