From 0901c77a7195e9fe9d5aefc2d6107d896c7d45cd Mon Sep 17 00:00:00 2001 From: Jean-Pierre Briede Date: Wed, 19 Feb 2025 15:34:52 -0800 Subject: [PATCH 1/2] Adding interface, implementation, and tests for vulnerability capabilities for a package domain model --- .../Models/Package/IVulnerable.cs | 18 ++++ .../Models/Package/VulnerableCapability.cs | 46 ++++++++++ .../Package/VulnerableCapabilityTests.cs | 90 +++++++++++++++++++ 3 files changed, 154 insertions(+) create mode 100644 src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs create mode 100644 src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs create mode 100644 test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs new file mode 100644 index 00000000000..50496145b4a --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using NuGet.Protocol; +using NuGet.VisualStudio.Internal.Contracts; + +namespace NuGet.PackageManagement.UI +{ + interface IVulnerable + { + public IEnumerable Vulnerabilities { get; } + + public bool IsVulnerable { get; } + + public PackageVulnerabilitySeverity VulnerabilityMaxSeverity { get; } + } +} diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs new file mode 100644 index 00000000000..eb93898d50a --- /dev/null +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs @@ -0,0 +1,46 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using NuGet.Protocol; +using NuGet.VisualStudio.Internal.Contracts; + +namespace NuGet.PackageManagement.UI +{ + public class VulnerableCapability : IVulnerable + { + private IEnumerable _vulnerabilities; + + public IEnumerable Vulnerabilities + { + get => _vulnerabilities; + private set => _vulnerabilities = value.OrderByDescending(v => v?.Severity ?? -1); + } + + public bool IsVulnerable => Vulnerabilities.Any(v => v != null); + + public PackageVulnerabilitySeverity VulnerabilityMaxSeverity + { + get + { + // Vulnerabilities are ordered so the first element is always the highest severity + int? severity = Vulnerabilities.FirstOrDefault()?.Severity; + if (severity != null && Enum.IsDefined(typeof(PackageVulnerabilitySeverity), severity)) + { + return (PackageVulnerabilitySeverity)severity; + } + else + { + return PackageVulnerabilitySeverity.Unknown; + } + } + } + + public VulnerableCapability(IEnumerable vulnerabilities) + { + Vulnerabilities = vulnerabilities; + } + } +} diff --git a/test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs b/test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs new file mode 100644 index 00000000000..eb0475c536d --- /dev/null +++ b/test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using NuGet.Protocol; +using NuGet.VisualStudio.Internal.Contracts; +using Xunit; + +namespace NuGet.PackageManagement.UI.Test +{ + public class VulnerableCapabilityTests + { + [Theory] + [InlineData(1, true)] + [InlineData(0, false)] + public void IsVulnerable_VariousVulnerabilities_ReturnsExpected(int severity, bool expected) + { + // Arrange + IEnumerable vulnerabilities = severity > 0 + ? [new(new Uri("http://example.com"), severity)] + : []; + var vulnerableCapability = new VulnerableCapability(vulnerabilities); + + // Act + var result = vulnerableCapability.IsVulnerable; + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void IsVulnerable_NullVulnerabilityInCollection_ReturnsFalse() + { + // Arrange + PackageVulnerabilityMetadataContextInfo[] vulnerabilities = [null]; + var vulnerableCapability = new VulnerableCapability(vulnerabilities); + + // Act + var result = vulnerableCapability.IsVulnerable; + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(new int[] { (int)PackageVulnerabilitySeverity.Low, (int)PackageVulnerabilitySeverity.High }, PackageVulnerabilitySeverity.High)] + [InlineData(new int[] { (int)PackageVulnerabilitySeverity.Critical, (int)PackageVulnerabilitySeverity.Unknown }, PackageVulnerabilitySeverity.Critical)] + [InlineData(new int[] { }, PackageVulnerabilitySeverity.Unknown)] + [InlineData(new int[] { -2 }, PackageVulnerabilitySeverity.Unknown)] + public void VulnerabilityMaxSeverity_VariousVulnerabilities_ReturnsExpected(int[] severities, PackageVulnerabilitySeverity expected) + { + // Arrange + var vulnerabilities = severities.Select(severity => new PackageVulnerabilityMetadataContextInfo(new Uri("http://example.com"), severity)).ToList(); + var vulnerableCapability = new VulnerableCapability(vulnerabilities); + + // Act + var result = vulnerableCapability.VulnerabilityMaxSeverity; + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void Constructor_OrdersVulnerabilitiesBySeverity_DescendingOrder() + { + // Arrange + var vulnerabilities = new List + { + new(new Uri("http://example.com/high"), (int)PackageVulnerabilitySeverity.High), + new(new Uri("http://example.com/low"), (int)PackageVulnerabilitySeverity.Low), + new(new Uri("http://example.com/moderate"), (int)PackageVulnerabilitySeverity.Moderate), + new(new Uri("http://example.com/critical"), (int)PackageVulnerabilitySeverity.Critical) + }; + + // Act + var vulnerableCapability = new VulnerableCapability(vulnerabilities); + var orderedVulnerabilities = vulnerableCapability.Vulnerabilities.ToList(); + + // Assert + Assert.Collection(orderedVulnerabilities, + item => Assert.Equal((int)PackageVulnerabilitySeverity.Critical, item.Severity), + item => Assert.Equal((int)PackageVulnerabilitySeverity.High, item.Severity), + item => Assert.Equal((int)PackageVulnerabilitySeverity.Moderate, item.Severity), + item => Assert.Equal((int)PackageVulnerabilitySeverity.Low, item.Severity) + ); + } + } +} From a4e0382ad536e024b0715c621773af2464df3343 Mon Sep 17 00:00:00 2001 From: Jean-Pierre Briede Date: Thu, 20 Feb 2025 12:35:56 -0800 Subject: [PATCH 2/2] Enable nullable and enforce required paramaters --- .../Models/Package/IVulnerable.cs | 2 ++ .../Models/Package/VulnerableCapability.cs | 6 ++++-- .../Models/Package/VulnerableCapabilityTests.cs | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs index 50496145b4a..f634aa55880 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/IVulnerable.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + using System.Collections.Generic; using NuGet.Protocol; using NuGet.VisualStudio.Internal.Contracts; diff --git a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs index eb93898d50a..d857b8351fe 100644 --- a/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs +++ b/src/NuGet.Clients/NuGet.PackageManagement.UI/Models/Package/VulnerableCapability.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -11,7 +13,7 @@ namespace NuGet.PackageManagement.UI { public class VulnerableCapability : IVulnerable { - private IEnumerable _vulnerabilities; + private IEnumerable _vulnerabilities = []; public IEnumerable Vulnerabilities { @@ -40,7 +42,7 @@ public PackageVulnerabilitySeverity VulnerabilityMaxSeverity public VulnerableCapability(IEnumerable vulnerabilities) { - Vulnerabilities = vulnerabilities; + Vulnerabilities = vulnerabilities ?? throw new ArgumentNullException(nameof(vulnerabilities)); } } } diff --git a/test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs b/test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs index eb0475c536d..336575090fc 100644 --- a/test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs +++ b/test/NuGet.Clients.Tests/NuGet.PackageManagement.UI.Test/Models/Package/VulnerableCapabilityTests.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +#nullable enable + using System; using System.Collections.Generic; using System.Linq; @@ -34,7 +36,7 @@ public void IsVulnerable_VariousVulnerabilities_ReturnsExpected(int severity, bo public void IsVulnerable_NullVulnerabilityInCollection_ReturnsFalse() { // Arrange - PackageVulnerabilityMetadataContextInfo[] vulnerabilities = [null]; + List vulnerabilities = [null]; var vulnerableCapability = new VulnerableCapability(vulnerabilities); // Act