-
Couldn't load subscription status.
- Fork 177
Fix SbomValidator.ValidateSbomAsync false positive when outputPath is directory #1131
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 11 commits
09406d2
bb2706b
4bfb55d
ec28a81
09bf8fd
271cf31
399220a
7e118b0
76b439c
c97f2d1
19deec3
d53a3bc
3f1cae3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| // Copyright (c) Microsoft. All rights reserved. | ||
| // Licensed under the MIT license. See LICENSE file in the project root for full license information. | ||
|
|
||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Linq; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.Sbom.Api.Entities; | ||
| using Microsoft.Sbom.Api.Entities.Output; | ||
| using Microsoft.Sbom.Api.Output.Telemetry; | ||
| using Microsoft.Sbom.Api.Workflows; | ||
| using Microsoft.Sbom.Common; | ||
| using Microsoft.Sbom.Common.Config; | ||
| using Microsoft.Sbom.Common.Config.Validators; | ||
| using Microsoft.Sbom.Contracts; | ||
| using Microsoft.Sbom.Extensions; | ||
| using Microsoft.Sbom.Extensions.Entities; | ||
| using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
| using Moq; | ||
| using Constants = Microsoft.Sbom.Api.Utils.Constants; | ||
|
|
||
| namespace Microsoft.Sbom.Api.Tests; | ||
|
|
||
| [TestClass] | ||
| public class SbomValidatorTests | ||
| { | ||
| private Mock<IWorkflow<SbomParserBasedValidationWorkflow>> workflowMock; | ||
| private Mock<IRecorder> recorderMock; | ||
| private Mock<IEnumerable<ConfigValidator>> configValidatorsMock; | ||
| private Mock<IConfiguration> configurationMock; | ||
| private Mock<ISbomConfigProvider> sbomConfigProviderMock; | ||
| private Mock<IFileSystemUtils> fileSystemUtilsMock; | ||
| private Mock<ISbomConfig> sbomConfigMock; | ||
| private SbomValidator sbomValidator; | ||
|
|
||
| // Common test data | ||
| private readonly string buildDropPath = "/test/drop"; | ||
| private readonly string outputPathFile = "/test/output.json"; | ||
| private readonly string outputPathDirectory = "/test/output"; | ||
| private readonly List<SbomSpecification> specifications = new List<SbomSpecification> { new SbomSpecification("SPDX", "2.2") }; | ||
| private readonly string manifestDirPath = "/test/manifest"; | ||
| private readonly ManifestInfo manifestInfo = Constants.TestManifestInfo; | ||
| private readonly string manifestJsonPath = "/test/manifest/manifest.json"; | ||
|
|
||
| [TestInitialize] | ||
| public void Init() | ||
| { | ||
| workflowMock = new Mock<IWorkflow<SbomParserBasedValidationWorkflow>>(MockBehavior.Strict); | ||
| recorderMock = new Mock<IRecorder>(MockBehavior.Strict); | ||
| configValidatorsMock = new Mock<IEnumerable<ConfigValidator>>(MockBehavior.Strict); | ||
| configurationMock = new Mock<IConfiguration>(MockBehavior.Strict); | ||
| sbomConfigProviderMock = new Mock<ISbomConfigProvider>(MockBehavior.Strict); | ||
| fileSystemUtilsMock = new Mock<IFileSystemUtils>(MockBehavior.Strict); | ||
| sbomConfigMock = new Mock<ISbomConfig>(MockBehavior.Strict); | ||
|
|
||
| sbomValidator = new SbomValidator( | ||
| workflowMock.Object, | ||
| recorderMock.Object, | ||
| configValidatorsMock.Object, | ||
| configurationMock.Object, | ||
| sbomConfigProviderMock.Object, | ||
| fileSystemUtilsMock.Object); | ||
| } | ||
DaveTryon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| [TestCleanup] | ||
| public void AfterEachTest() | ||
| { | ||
| workflowMock.VerifyAll(); | ||
| recorderMock.VerifyAll(); | ||
| configValidatorsMock.VerifyAll(); | ||
| configurationMock.VerifyAll(); | ||
| sbomConfigProviderMock.VerifyAll(); | ||
| fileSystemUtilsMock.VerifyAll(); | ||
| sbomConfigMock.VerifyAll(); | ||
| } | ||
|
|
||
| private void SetupMocksForValidation(List<FileValidationResult> errors, List<Exception> exceptions) | ||
| { | ||
| configValidatorsMock.Setup(cv => cv.GetEnumerator()).Returns(new List<ConfigValidator>().GetEnumerator()); | ||
|
|
||
| configurationMock.Setup(c => c.ManifestInfo).Returns(new ConfigurationSetting<IList<ManifestInfo>> | ||
DaveTryon marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| { | ||
| Value = new List<ManifestInfo> { manifestInfo } | ||
| }); | ||
|
|
||
| sbomConfigProviderMock.Setup(scp => scp.Get(manifestInfo)).Returns(sbomConfigMock.Object); | ||
| sbomConfigMock.Setup(sc => sc.ManifestJsonFilePath).Returns(manifestJsonPath); | ||
|
|
||
| fileSystemUtilsMock.Setup(fs => fs.FileExists(manifestJsonPath)).Returns(true); | ||
| workflowMock.Setup(w => w.RunAsync()).ReturnsAsync(true); | ||
|
|
||
| recorderMock.Setup(r => r.FinalizeAndLogTelemetryAsync()).Returns(Task.CompletedTask); | ||
| recorderMock.Setup(r => r.Errors).Returns(errors); | ||
|
|
||
| // Determine the result based on whether there are errors or exceptions | ||
| var result = (errors.Any() || exceptions.Any()) ? Result.Failure : Result.Success; | ||
| recorderMock.Setup(r => r.Result).Returns(result); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task ValidateSbomAsync_WithNoErrorsAndNoExceptions_ReturnsTrue() | ||
| { | ||
| var errors = new List<FileValidationResult>(); | ||
| var exceptions = new List<Exception>(); | ||
|
|
||
| SetupMocksForValidation(errors, exceptions); | ||
|
|
||
| var result = await sbomValidator.ValidateSbomAsync(buildDropPath, outputPathFile, specifications, manifestDirPath); | ||
|
|
||
| Assert.IsTrue(result.IsSuccess); | ||
| Assert.AreEqual(0, result.Errors.Count); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task ValidateSbomAsync_WithErrorsButNoExceptions_ReturnsFalse() | ||
| { | ||
| var errors = new List<FileValidationResult> | ||
| { | ||
| new FileValidationResult { ErrorType = ErrorType.MissingFile, Path = "/test/missing.txt" } | ||
| }; | ||
| var exceptions = new List<Exception>(); | ||
|
|
||
| SetupMocksForValidation(errors, exceptions); | ||
|
|
||
| var result = await sbomValidator.ValidateSbomAsync(buildDropPath, outputPathFile, specifications, manifestDirPath); | ||
|
|
||
| Assert.IsFalse(result.IsSuccess); | ||
| Assert.AreEqual(1, result.Errors.Count); | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task ValidateSbomAsync_WithNoErrorsButWithExceptions_ReturnsFalse() | ||
| { | ||
| var errors = new List<FileValidationResult>(); | ||
| var exceptions = new List<Exception> | ||
| { | ||
| new InvalidOperationException("Cannot write to directory path") | ||
| }; | ||
|
|
||
| SetupMocksForValidation(errors, exceptions); | ||
|
|
||
| var result = await sbomValidator.ValidateSbomAsync(buildDropPath, outputPathDirectory, specifications, manifestDirPath); | ||
|
|
||
| Assert.IsFalse(result.IsSuccess); | ||
| Assert.AreEqual(0, result.Errors.Count); // No validation errors, but should still fail due to exception | ||
| } | ||
|
|
||
| [TestMethod] | ||
| public async Task ValidateSbomAsync_WithBothErrorsAndExceptions_ReturnsFalse() | ||
| { | ||
| var errors = new List<FileValidationResult> | ||
| { | ||
| new FileValidationResult { ErrorType = ErrorType.MissingFile, Path = "/test/missing.txt" } | ||
| }; | ||
| var exceptions = new List<Exception> | ||
| { | ||
| new InvalidOperationException("Cannot write to directory path") | ||
| }; | ||
|
|
||
| SetupMocksForValidation(errors, exceptions); | ||
|
|
||
| var result = await sbomValidator.ValidateSbomAsync(buildDropPath, outputPathDirectory, specifications, manifestDirPath); | ||
|
|
||
| Assert.IsFalse(result.IsSuccess); | ||
| Assert.AreEqual(1, result.Errors.Count); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,7 @@ | |
| using Microsoft.Sbom.Api.Config.Args; | ||
| using Microsoft.Sbom.Api.Convertors; | ||
| using Microsoft.Sbom.Api.Entities; | ||
| using Microsoft.Sbom.Api.Entities.Output; | ||
| using Microsoft.Sbom.Api.Executors; | ||
| using Microsoft.Sbom.Api.Filters; | ||
| using Microsoft.Sbom.Api.FormatValidator; | ||
|
|
@@ -128,6 +129,10 @@ private class PinnedIRecorder : IRecorder | |
| { | ||
| public IList<FileValidationResult> Errors => throw new NotImplementedException(); | ||
|
|
||
| public IList<Exception> Exceptions => throw new NotImplementedException(); | ||
|
||
|
|
||
| public Result Result => throw new NotImplementedException(); | ||
|
|
||
| public void AddResult(string propertyName, string value) => throw new NotImplementedException(); | ||
| public void AddToTotalCountOfLicenses(int count) => throw new NotImplementedException(); | ||
| public void AddToTotalNumberOfPackageDetailsEntries(int count) => throw new NotImplementedException(); | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
testException1should be at index 0.testException2should be at index 1There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed the assertions to check specific indices instead of just containment. Now verifying that testException1 is at index 0 and testException2 is at index 1, which provides a more precise test of the storage order. d53a3bc