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
@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
Expand Down Expand Up @@ -87,6 +87,9 @@ public ICollection<string> GetExplicitReferencedDependencyIds(string componentId
return explicitReferencedDependencyIds;
}

/// <summary>
/// Any file added here will be reported as a location on ALL components found in current graph.
/// </summary>
public void AddAdditionalRelatedFile(string additionalRelatedFile)
{
this.AdditionalRelatedFiles.AddOrUpdate(additionalRelatedFile, 0, (notUsed, notUsed2) => 0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public DetectedComponent(TypedComponent.TypedComponent component, IComponentDete

private string DebuggerDisplay => $"{this.Component.DebuggerDisplay}";

/// <summary>Adds a filepath to the FilePaths hashset for this detected component.</summary>
/// <summary>Adds a filepath to the FilePaths hashset for this detected component.
/// Note: Dependency Graph automatically captures the location where a component is found, no need to call it at all inside package manager detectors.</summary>
/// <param name="filePath">The file path to add to the hashset.</param>
public void AddComponentFilePath(string filePath)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Microsoft.ComponentDetection.Contracts;
namespace Microsoft.ComponentDetection.Contracts;
using System.Collections.Generic;
using Microsoft.ComponentDetection.Contracts.BcdeModels;

Expand Down Expand Up @@ -45,6 +45,9 @@ void RegisterUsage(

DetectedComponent GetComponent(string componentId);

/// <summary>
/// Any file added here will be reported as a location on ALL components found in current graph.
/// </summary>
void AddAdditionalRelatedFile(string relatedFilePath);

IReadOnlyDictionary<string, DetectedComponent> GetDetectedComponents();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace Microsoft.ComponentDetection.Orchestrator.Services.GraphTranslation;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Orchestrator.ArgumentSets;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

public class DefaultGraphTranslationService : IGraphTranslationService
{
Expand Down Expand Up @@ -70,7 +71,9 @@ private IEnumerable<DetectedComponent> GatherSetOfDetectedComponentsUnmerged(IEn
// to look like a pipeline.
foreach (var component in detectedComponents)
{
// Reinitialize properties that might still be getting populated in ways we don't want to support, because the data is authoritatively stored in the graph.
// clone custom locations and make them relative to root.
var declaredRawFilePaths = component.FilePaths ?? new HashSet<string>();
var componentCustomLocations = JsonConvert.DeserializeObject<HashSet<string>>(JsonConvert.SerializeObject(declaredRawFilePaths));
component.FilePaths?.Clear();

// Information about each component is relative to all of the graphs it is present in, so we take all graphs containing a given component and apply the graph data.
Expand All @@ -87,8 +90,15 @@ private IEnumerable<DetectedComponent> GatherSetOfDetectedComponentsUnmerged(IEn

// Return in a format that allows us to add the additional files for the components
var locations = dependencyGraph.GetAdditionalRelatedFiles();

// graph authoritatively stores the location of the component
locations.Add(location);

foreach (var customLocation in componentCustomLocations)
{
locations.Add(customLocation);
}

var relativePaths = this.MakeFilePathsRelative(this.logger, rootDirectory, locations);

foreach (var additionalRelatedFile in relativePaths ?? Enumerable.Empty<string>())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
namespace Microsoft.ComponentDetection.Orchestrator.Tests.Services;

using System.Collections.Generic;
using System.IO;
using System.Linq;
using FluentAssertions;
using Microsoft.ComponentDetection.Common.DependencyGraph;
using Microsoft.ComponentDetection.Contracts;
using Microsoft.ComponentDetection.Contracts.BcdeModels;
using Microsoft.ComponentDetection.Contracts.TypedComponent;
using Microsoft.ComponentDetection.Orchestrator.ArgumentSets;
using Microsoft.ComponentDetection.Orchestrator.Services;
using Microsoft.ComponentDetection.Orchestrator.Services.GraphTranslation;
using Microsoft.Extensions.Logging;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;

[TestClass]
[TestCategory("Governance/All")]
[TestCategory("Governance/ComponentDetection")]
public class DefaultGraphTranslationServiceTests
{
private readonly DefaultGraphTranslationService serviceUnderTest;
private readonly ContainerDetails sampleContainerDetails;
private readonly ComponentRecorder componentRecorder;
private readonly Mock<IComponentDetector> componentDetectorMock;
private readonly DirectoryInfo sourceDirectory;

public DefaultGraphTranslationServiceTests()
{
this.serviceUnderTest = new DefaultGraphTranslationService(new Mock<ILogger<DefaultGraphTranslationService>>().Object);
this.componentRecorder = new ComponentRecorder(new Mock<ILogger>().Object);

this.sampleContainerDetails = new ContainerDetails { Id = 1 };
this.componentDetectorMock = new Mock<IComponentDetector>();
this.componentDetectorMock.SetupGet(x => x.Id).Returns("Detector1");
this.componentDetectorMock.SetupGet(x => x.Version).Returns(1);
this.sourceDirectory = new DirectoryInfo(Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()));
this.sourceDirectory.Create();
}

[TestMethod]
public void GenerateScanResultFromResult_WithCustomLocations()
{
var detectedFilePath = "/some/file/path";
var npmCustomPath = "/custom/path.js";
var nugetCustomPath = "/custom/path2.csproj";
var relatedFilePath = "/generic/relevant/path";

var singleFileComponentRecorder = this.componentRecorder.CreateSingleFileComponentRecorder(Path.Join(this.sourceDirectory.FullName, detectedFilePath));
var processingResult = new DetectorProcessingResult
{
ResultCode = ProcessingResultCode.Success,
ContainersDetailsMap = new Dictionary<int, ContainerDetails>
{
{
this.sampleContainerDetails.Id, this.sampleContainerDetails
},
},
ComponentRecorders = new[] { (this.componentDetectorMock.Object, this.componentRecorder) },
};

var expectedNpmComponent = new NpmComponent("npm-component", "1.2.3");
var expectedNugetComponent = new NuGetComponent("nugetComponent", "4.5.6");
var detectedNpmComponent = new DetectedComponent(expectedNpmComponent);
var detectedNugetComponent = new DetectedComponent(expectedNugetComponent);

// Any Related File will be reported for ALL components found in this graph
singleFileComponentRecorder.AddAdditionalRelatedFile(Path.Join(this.sourceDirectory.FullName, relatedFilePath));

// Registering components in same manifest with different custom paths
detectedNpmComponent.AddComponentFilePath(Path.Join(this.sourceDirectory.FullName, npmCustomPath));
detectedNugetComponent.AddComponentFilePath(Path.Join(this.sourceDirectory.FullName, nugetCustomPath));

singleFileComponentRecorder.RegisterUsage(detectedNpmComponent, isDevelopmentDependency: false);
singleFileComponentRecorder.RegisterUsage(detectedNugetComponent, isDevelopmentDependency: true);

var args = new BcdeArguments
{
SourceDirectory = this.sourceDirectory,
};

var result = this.serviceUnderTest.GenerateScanResultFromProcessingResult(processingResult, args);
result.Should().NotBeNull();
result.ComponentsFound.Should().HaveCount(2);
result.ResultCode.Should().Be(ProcessingResultCode.Success);

var resultNpmComponent = result.ComponentsFound.Single(c => c.Component.Type == ComponentType.Npm);
var resultNugetComponent = result.ComponentsFound.Single(c => c.Component.Type == ComponentType.NuGet);

resultNpmComponent.LocationsFoundAt.Should().BeEquivalentTo(new[] { npmCustomPath, detectedFilePath, relatedFilePath });
resultNugetComponent.LocationsFoundAt.Should().BeEquivalentTo(new[] { nugetCustomPath, detectedFilePath, relatedFilePath });

var actualNpmComponent = resultNpmComponent.Component as NpmComponent;
var actualNugetComponent = resultNugetComponent.Component as NuGetComponent;

actualNpmComponent.Should().BeEquivalentTo(expectedNpmComponent);
actualNugetComponent.Should().BeEquivalentTo(expectedNugetComponent);
}
}