Skip to content
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

Include project-references as components in SBOM (Issue #785) #787

Merged
merged 22 commits into from
Dec 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
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
19 changes: 19 additions & 0 deletions CycloneDX.Tests/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System.Runtime.InteropServices;
using Xunit;

// In SDK-style projects such as this one, several assembly attributes that were historically
// defined in this file are now automatically added during build and populated with
// values defined in project properties. For details of which attributes are included
// and how to customise this process see: https://aka.ms/assembly-info-properties


// Setting ComVisible to false makes the types in this assembly not visible to COM
// components. If you need to access a type in this assembly from COM, set the ComVisible
// attribute to true on that type.

[assembly: ComVisible(false)]

// The following GUID is for the ID of the typelib if this project is exposed to COM.

[assembly: Guid("7c5fdba2-af25-4677-bcc0-90636fca4c44")]
[assembly: CollectionBehavior(DisableTestParallelization = true)]
12 changes: 6 additions & 6 deletions CycloneDX.Tests/ComponentServiceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,20 @@ public async Task RecursivelyGetComponents_ReturnsComponents()
{
var mockNugetService = new Mock<INugetService>();
mockNugetService
.SetupSequence(service => service.GetComponentAsync(It.IsAny<NugetPackage>()))
.SetupSequence(service => service.GetComponentAsync(It.IsAny<DotnetDependency>()))
.ReturnsAsync(new Component { Name = "Package1", Version = "1.0.0" })
.ReturnsAsync(new Component { Name = "Package2", Version = "1.0.0" })
.ReturnsAsync(new Component { Name = "Package3", Version = "1.0.0" });
var nugetService = mockNugetService.Object;
var componentService = new ComponentService(nugetService);
var nugetPackages = new List<NugetPackage>
var DotnetDependencys = new List<DotnetDependency>
{
new NugetPackage { Name = "Package1", Version = "1.0.0" },
new NugetPackage { Name = "Package2", Version = "1.0.0" },
new NugetPackage { Name = "Package3", Version = "1.0.0" },
new DotnetDependency { Name = "Package1", Version = "1.0.0" },
new DotnetDependency { Name = "Package2", Version = "1.0.0" },
new DotnetDependency { Name = "Package3", Version = "1.0.0" },
};

var components = await componentService.RecursivelyGetComponentsAsync(nugetPackages).ConfigureAwait(false);
var components = await componentService.RecursivelyGetComponentsAsync(DotnetDependencys).ConfigureAwait(false);
var sortedComponents = components.OrderBy(c => c.Name).ToList();

Assert.Collection(sortedComponents,
Expand Down
12 changes: 12 additions & 0 deletions CycloneDX.Tests/CycloneDX.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,18 @@
</ItemGroup>

<ItemGroup>
<None Update="FunctionalTests\TestcaseFiles\Issue-758.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="FunctionalTests\TestcaseFiles\ProjectReferenceWithPackageReferenceWithTransitivePackage.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="FunctionalTests\TestcaseFiles\SimpleNET6.0Library.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="FunctionalTests\TestcaseFiles\SimpleNETStandardLibrary.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Resources\metadata\cycloneDX-metadata-template.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
Expand Down
119 changes: 119 additions & 0 deletions CycloneDX.Tests/FunctionalTests/FunctionalTestHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.IO.Abstractions.TestingHelpers;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CycloneDX.Interfaces;
using CycloneDX.Models;
using CycloneDX.Services;
using Moq;
using Xunit;
using static CycloneDX.Models.Component;

namespace CycloneDX.Tests.FunctionalTests
{
public static class FunctionalTestHelper
{
private static INugetServiceFactory CreateMockNugetServiceFactory()
{
var mockNugetService = new Mock<INugetService>();
mockNugetService.Setup(s => s.GetComponentAsync(
It.IsAny<DotnetDependency>()))
.Returns((DotnetDependency dep) => Task.FromResult
(new Component
{
Name = dep.Name,
Version = dep.Version,
Type = Classification.Library,
BomRef = $"pkg:nuget/{dep.Name}@{dep.Version}"
}));

var nugetService = mockNugetService.Object;

var mockNugetServiceFactory = new Mock<INugetServiceFactory>();
mockNugetServiceFactory.Setup(s => s.Create(
It.IsAny<RunOptions>(),
It.IsAny<IFileSystem>(),
It.IsAny<IGithubService>(),
It.IsAny<List<string>>()))
.Returns(nugetService);

return mockNugetServiceFactory.Object;
}


public static async Task<Bom> Test(string assetsJson, RunOptions options) => await Test(assetsJson, options, null);

/// <summary>
/// Trying to build SBOM from provided parameters and validated the result file
/// </summary>
/// <param name="assetsJson"></param>
/// <param name="options"></param>
/// <returns></returns>
public static async Task<Bom> Test(string assetsJson, RunOptions options, INugetServiceFactory nugetService)
{
options.disableGithubLicenses = true;
options.outputDirectory ??= "/bom/";
options.outputFilename ??= options.json ? "bom.json" : "bom.xml";
options.SolutionOrProjectFile ??= MockUnixSupport.Path("c:/ProjectPath/Project.csproj");
options.disablePackageRestore = true;

nugetService ??= CreateMockNugetServiceFactory();


var mockFileSystem = new MockFileSystem(new Dictionary<string, MockFileData>
{
{ MockUnixSupport.Path(options.SolutionOrProjectFile), new MockFileData(CsprojContents) },
{ MockUnixSupport.Path("c:/ProjectPath/obj/project.assets.json"), new MockFileData(assetsJson) }
});


Runner runner = new Runner(mockFileSystem, null, null, null, null, null, null, nugetService);
int exitCode = await runner.HandleCommandAsync(options);

Assert.Equal((int)ExitCode.OK, exitCode);

var expectedFileName = mockFileSystem.Path.Combine(options.outputDirectory, options.outputFilename);



Assert.True(mockFileSystem.FileExists(MockUnixSupport.Path(expectedFileName)), "Bom file not generated");

var mockBomFile = mockFileSystem.GetFile(MockUnixSupport.Path(expectedFileName));
var mockBomFileStream = new MemoryStream(mockBomFile.Contents);
ValidationResult validationResult;
if (options.json)
{
validationResult = await Json.Validator.ValidateAsync(mockBomFileStream, SpecificationVersion.v1_5).ConfigureAwait(false);
}
else
{
validationResult = Xml.Validator.Validate(mockBomFileStream, SpecificationVersion.v1_5);
}
Assert.True(validationResult.Valid);

return runner.LastGeneratedBom;
}


private const string CsprojContents =
"<Project Sdk=\"Microsoft.NET.Sdk\">\n\n " +
"<PropertyGroup>\n " +
"<OutputType>Exe</OutputType>\n " +
"<PackageId>SampleProject</PackageId>\n " +
"</PropertyGroup>\n\n " +
"<ItemGroup>\n " +
"</ItemGroup>\n" +
"</Project>\n";

public static void AssertHasDependencyWithChild(Bom bom, string dependencyBomRef, string childBomRef)
=> AssertHasDependencyWithChild(bom, dependencyBomRef, childBomRef, null);
public static void AssertHasDependencyWithChild(Bom bom, string dependencyBomRef, string childBomRef, string message)
{
Assert.True(bom.Dependencies.Any(dep => dep.Ref == dependencyBomRef && dep.Dependencies.Any(child => child.Ref == childBomRef)), message);
}
}
}
78 changes: 78 additions & 0 deletions CycloneDX.Tests/FunctionalTests/Issue758.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Threading.Tasks;
using CycloneDX.Interfaces;
using CycloneDX.Models;
using CycloneDX.Services;
using Moq;
using Xunit;
using static CycloneDX.Models.Component;

namespace CycloneDX.Tests.FunctionalTests
{
public class Issue758
{
readonly INugetServiceFactory nugetServiceFactory;

public Issue758()
{
var mockNugetService = new Mock<INugetService>();
mockNugetService.Setup(s => s.GetComponentAsync(
It.Is<DotnetDependency>(dep =>
dep.Name == "User.Dependent" &&
dep.Version == "1.0.0")))
.Returns(Task.FromResult
(new Component {
Name = "User.Dependent",
Version = "1.0.0",
Type = Classification.Library,
BomRef = "pkg:nuget/[email protected]"
}));

var nugetService = mockNugetService.Object;

var mockNugetServiceFactory = new Mock<INugetServiceFactory>();
mockNugetServiceFactory.Setup(s => s.Create(
It.IsAny<RunOptions>(),
It.IsAny<IFileSystem>(),
It.IsAny<IGithubService>(),
It.IsAny<List<string>>()))
.Returns(nugetService);

nugetServiceFactory = mockNugetServiceFactory.Object;
}


[Fact(Timeout = 15000)]
public async Task Issue758_BaseCase()
{
var assetsJson = File.ReadAllText(Path.Combine("FunctionalTests", "TestcaseFiles", "Issue-758.json"));
var options = new RunOptions
{
};

var bom = await FunctionalTestHelper.Test(assetsJson, options, nugetServiceFactory);

Assert.True(bom.Components.Count == 1);
Assert.Contains(bom.Components, c => string.Compare(c.Name, "User.Dependent", true) == 0 && c.Version == "1.0.0");
Assert.DoesNotContain(bom.Components, c => string.Compare(c.Name, "User.Dependency", true) == 0 );
}

[Fact(Timeout = 15000)]
public async Task Issue758_IPR()
{
var assetsJson = File.ReadAllText(Path.Combine("FunctionalTests", "TestcaseFiles", "Issue-758.json"));
var options = new RunOptions
{
includeProjectReferences = true
};

var bom = await FunctionalTestHelper.Test(assetsJson, options, nugetServiceFactory);

Assert.True(bom.Components.Count == 2);
Assert.Contains(bom.Components, c => string.Compare(c.Name, "User.Dependent", true) == 0 && c.Version == "1.0.0");
Assert.Contains(bom.Components, c => string.Compare(c.Name, "User.Dependency", true) == 0 && c.Version == "1.0.0");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.CommandLine;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CycloneDX.Models;
using Xunit;

namespace CycloneDX.Tests.FunctionalTests
{
public class ProjectReferenceWithPackageReferenceWithTransitivePackage
{
readonly string assetsJson = File.ReadAllText(Path.Combine("FunctionalTests", "TestcaseFiles", "ProjectReferenceWithPackageReferenceWithTransitivePackage.json"));
readonly RunOptions options = new RunOptions
{};

[Fact(Timeout = 15000)]
public async Task ProjectReferenceWithPackageReferenceWithTransitivePackage_BaseCase()
{
var bom = await FunctionalTestHelper.Test(assetsJson, options);

Assert.True(bom.Components.Count == 3, "Unexpected amount of components");
Assert.Contains(bom.Components, c => string.Compare(c.Name, "Castle.Core", true) == 0 && c.Version == "5.1.1");
}

[Fact(Timeout = 15000)]
public async Task ProjectReferenceWithPackageReferenceWithTransitivePackage_includeProjectReferences()
{
options.includeProjectReferences = true;

var bom = await FunctionalTestHelper.Test(assetsJson, options);

Assert.True(bom.Components.Count == 4, "Unexpected amount of components");
Assert.Contains(bom.Components, c => string.Compare(c.Name, "ClassLibrary1", true) == 0 && c.Version == "1.0.0");
Assert.Contains(bom.Components, c => string.Compare(c.Name, "Castle.Core", true) == 0 && c.Version == "5.1.1");

FunctionalTestHelper.AssertHasDependencyWithChild(bom, "[email protected]", "pkg:nuget/[email protected]", "expected dependency not found");
}
}
}
26 changes: 26 additions & 0 deletions CycloneDX.Tests/FunctionalTests/SimpleNET6.0Library.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.IO;
using System.Threading.Tasks;
using CycloneDX.Models;
using Xunit;

namespace CycloneDX.Tests.FunctionalTests
{
public class SimpleNET6_0Library
{
[Fact(Timeout = 15000)]
public async Task TestSimpleNET6_0Library()
{
var assetsJson = File.ReadAllText(Path.Combine("FunctionalTests", "TestcaseFiles", "SimpleNET6.0Library.json"));
var options = new RunOptions
{
};


var bom = await FunctionalTestHelper.Test(assetsJson, options);

Assert.True(bom.Components.Count == 1);
Assert.Contains(bom.Components, c => string.Compare(c.Name, "newtonsoft.json", true) == 0 && c.Version == "13.0.3");
}
}
}
30 changes: 30 additions & 0 deletions CycloneDX.Tests/FunctionalTests/SimpleNETStandardLibrary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CycloneDX.Models;
using Xunit;


namespace CycloneDX.Tests.FunctionalTests
{
public class SimpleNETStandardLibrary
{
[Fact(Timeout = 15000)]
public async Task TestSimpleNETStandardLibrary()
{
var assetsJson = File.ReadAllText(Path.Combine("FunctionalTests", "TestcaseFiles", "SimpleNETStandardLibrary.json"));
var options = new RunOptions
{
};


var bom = await FunctionalTestHelper.Test(assetsJson, options);

Assert.True(bom.Components.Count == 1);
Assert.Contains(bom.Components, c => string.Compare(c.Name, "newtonsoft.json", true) == 0 && c.Version == "13.0.3");
}
}
}
Loading