From 271df7c79bd788ec4884180fd7c02d06392cfd36 Mon Sep 17 00:00:00 2001 From: George Pollard Date: Tue, 11 Apr 2023 10:02:41 +1200 Subject: [PATCH] Add test etc --- .../nuget/NuGetComponentDetector.cs | 14 +++- .../nuget/NuGetLockfileShape.cs | 11 ++- .../NuGetComponentDetectorTests.cs | 74 +++++++++++++++---- .../Utilities/TestLogger.cs | 26 +++++++ .../DetectorTestUtilityBuilder.cs | 7 ++ 5 files changed, 115 insertions(+), 17 deletions(-) create mode 100644 test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestLogger.cs diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs index 2b4a6a918..b670b7ab3 100644 --- a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetComponentDetector.cs @@ -195,10 +195,22 @@ private async Task ParseNugetLockfileAsync(ProcessRequest processRequest) var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; var stream = processRequest.ComponentStream; - var lockfile = await JsonSerializer.DeserializeAsync(stream.Stream); + NuGetLockfileShape lockfile; + try + { + lockfile = await JsonSerializer.DeserializeAsync(stream.Stream).ConfigureAwait(false); + } + catch (Exception e) + { + this.Logger.LogError(e, "Error loading NuGet lockfile from {Location}", stream.Location); + singleFileComponentRecorder.RegisterPackageParseFailure(stream.Location); + return; + } + if (lockfile.Version != 1) { // only version 1 is supported + this.Logger.LogError("Unsupported NuGet lockfile version {Version}", lockfile.Version); singleFileComponentRecorder.RegisterPackageParseFailure(stream.Location); return; } diff --git a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetLockfileShape.cs b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetLockfileShape.cs index 7c814c667..06bc663e7 100644 --- a/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetLockfileShape.cs +++ b/src/Microsoft.ComponentDetection.Detectors/nuget/NuGetLockfileShape.cs @@ -1,17 +1,22 @@ namespace Microsoft.ComponentDetection.Detectors.NuGet; using System.Collections.Generic; +using System.Text.Json.Serialization; -internal class NugetLockfileShape +internal record NuGetLockfileShape { + [JsonPropertyName("version")] public int Version { get; set; } - public Dictionary> Dependencies { get; set; } + [JsonPropertyName("dependencies")] + public Dictionary> Dependencies { get; set; } = new(); - public class PackageShape + public record PackageShape { + [JsonPropertyName("type")] public string Type { get; set; } + [JsonPropertyName("resolved")] public string Resolved { get; set; } } } diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs index 29c24ba69..0f84cb537 100644 --- a/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/NuGetComponentDetectorTests.cs @@ -12,6 +12,7 @@ using Microsoft.ComponentDetection.Contracts.Internal; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.ComponentDetection.Detectors.NuGet; +using Microsoft.ComponentDetection.Detectors.Tests.Utilities; using Microsoft.ComponentDetection.TestsUtilities; using Microsoft.Extensions.Logging; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -23,14 +24,17 @@ public class NuGetComponentDetectorTests : BaseDetectorTest { private static readonly IEnumerable DetectorSearchPattern = - new List { "*.nupkg", "*.nuspec", "nuget.config", "paket.lock" }; + new List { "*.nupkg", "*.nuspec", "nuget.config", "packages.lock.json", "paket.lock" }; - private readonly Mock> mockLogger; + private ILogger logger; - public NuGetComponentDetectorTests() + public TestContext TestContext { get; set; } + + [TestInitialize] + public void Setup() { - this.mockLogger = new Mock>(); - this.DetectorTestUtility.AddServiceMock(this.mockLogger); + this.logger = new TestLogger(this.TestContext); + this.DetectorTestUtility.AddService(this.logger); } [TestMethod] @@ -114,6 +118,57 @@ public async Task TestNugetDetector_ReturnsValidMixedComponentAsync() Assert.AreEqual(2, componentRecorder.GetDetectedComponents().Count()); } + [TestMethod] + public async Task TestNugetDetector_ReturnsPackagesLockfileAsync() + { + var lockfile = @"{ + ""version"": 1, + ""dependencies"": { + ""net7.0"": { + ""Azure.Core"": { + ""type"": ""Direct"", + ""requested"": ""[1.25.0, )"", + ""resolved"": ""1.25.0"", + ""contentHash"": ""X8Dd4sAggS84KScWIjEbFAdt2U1KDolQopTPoHVubG2y3CM54f9l6asVrP5Uy384NWXjsspPYaJgz5xHc+KvTA=="", + ""dependencies"": { + ""Microsoft.Bcl.AsyncInterfaces"": ""1.1.1"", + ""System.Diagnostics.DiagnosticSource"": ""4.6.0"", + ""System.Memory.Data"": ""1.0.2"", + ""System.Numerics.Vectors"": ""4.5.0"", + ""System.Text.Encodings.Web"": ""4.7.2"", + ""System.Text.Json"": ""4.7.2"", + ""System.Threading.Tasks.Extensions"": ""4.5.4"" + } + } + }, + ""net6.0"": { + ""Azure.Data.Tables"": { + ""type"": ""Direct"", + ""requested"": ""[12.5.0, )"", + ""resolved"": ""12.5.0"", + ""contentHash"": ""XeIxPf+rF1NXkX3NJSB0ZTNgU233vyPXGmaFsR0lUVibtWP/lj+Qu1FcPxoslURcX0KC+UgTb226nqVdHjoweQ=="", + ""dependencies"": { + ""Azure.Core"": ""1.22.0"", + ""System.Text.Json"": ""4.7.2"" + } + } + } + } +}"; + + var (scanResult, componentRecorder) = await this.DetectorTestUtility + .WithFile("packages.lock.json", lockfile) + .ExecuteDetectorAsync(); + + Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); + + // should be 2 components found; one per framework + var components = new HashSet(componentRecorder.GetDetectedComponents().Select(x => x.Component.Id)); + Assert.AreEqual(2, components.Count); + Assert.IsTrue(components.Contains("Azure.Core 1.25.0 - NuGet")); + Assert.IsTrue(components.Contains("Azure.Data.Tables 12.5.0 - NuGet")); + } + [TestMethod] public async Task TestNugetDetector_ReturnsValidPaketComponentAsync() { @@ -170,16 +225,9 @@ public async Task TestNugetDetector_HandlesMalformedComponentsInComponentListAsy .WithFile("test.nuspec", nuspec) .WithFile("test.nupkg", validNupkg) .WithFile("malformed.nupkg", malformedNupkg) - .AddServiceMock(this.mockLogger) + .AddService(this.logger) .ExecuteDetectorAsync(); - this.mockLogger.Verify(x => x.Log( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny(), - (Func)It.IsAny())); - Assert.AreEqual(ProcessingResultCode.Success, scanResult.ResultCode); Assert.AreEqual(2, componentRecorder.GetDetectedComponents().Count()); } diff --git a/test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestLogger.cs b/test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestLogger.cs new file mode 100644 index 000000000..eadaeb4a4 --- /dev/null +++ b/test/Microsoft.ComponentDetection.Detectors.Tests/Utilities/TestLogger.cs @@ -0,0 +1,26 @@ +namespace Microsoft.ComponentDetection.Detectors.Tests.Utilities; + +using System; +using Microsoft.Extensions.Logging; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +internal class TestLogger : ILogger, IDisposable +{ + private readonly TestContext context; + + public TestLogger(TestContext context) + => this.context = context; + + public IDisposable BeginScope(TState state) + where TState : notnull + => this; + + public void Dispose() + { + } + + public bool IsEnabled(LogLevel logLevel) => true; + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) + => this.context.WriteLine($"{logLevel} ({eventId}): {formatter(state, exception)}"); +} diff --git a/test/Microsoft.ComponentDetection.TestsUtilities/DetectorTestUtilityBuilder.cs b/test/Microsoft.ComponentDetection.TestsUtilities/DetectorTestUtilityBuilder.cs index 9c3c353db..36dd12e14 100644 --- a/test/Microsoft.ComponentDetection.TestsUtilities/DetectorTestUtilityBuilder.cs +++ b/test/Microsoft.ComponentDetection.TestsUtilities/DetectorTestUtilityBuilder.cs @@ -49,6 +49,13 @@ public DetectorTestUtilityBuilder() public DetectorTestUtilityBuilder WithFile(string fileName, string fileContents, IEnumerable searchPatterns = null, string fileLocation = null) => this.WithFile(fileName, fileContents.ToStream(), searchPatterns, fileLocation); + public DetectorTestUtilityBuilder AddService(TService it) + where TService : class + { + this.serviceCollection.AddSingleton(it); + return this; + } + public DetectorTestUtilityBuilder AddServiceMock(Mock mock) where TMock : class {