From fe0d7b8e287f5a70cf85d2fbf29a9cfc97fae6f2 Mon Sep 17 00:00:00 2001 From: ppandrate Date: Fri, 26 Sep 2025 10:36:27 -0700 Subject: [PATCH 1/9] include only direct packages and direct package dependencies --- .../Executors/ComponentDetectionBaseWalker.cs | 19 +++++++++++-- .../Executors/PackageInfoJsonWriter.cs | 27 ++++++++++--------- .../IntegrationTests.cs | 21 +++++++++++++++ 3 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs b/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs index 997abe723..0567d5686 100644 --- a/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs +++ b/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs @@ -113,7 +113,7 @@ async Task Scan(string path) var scanSettings = cliArgumentBuilder.BuildScanSettingsFromParsedArgs(cmdLineParams); - var scanResult = await componentDetector.ScanAsync(scanSettings); + var scanResult = await componentDetector.ScanAsync(scanSettings) as DefaultGraphScanResult; if (scanResult.ResultCode != ProcessingResultCode.Success) { @@ -123,6 +123,21 @@ async Task Scan(string path) var uniqueComponents = FilterScannedComponents(scanResult); + // Collect all explicitly referenced component IDs from all dependency graphs + var explicitComponentIds = new HashSet(); + + if (scanResult.DependencyGraphs != null) + { + foreach (var dependencyGraphPair in scanResult.DependencyGraphs) + { + var dependencyGraph = dependencyGraphPair.Value; + explicitComponentIds.UnionWith(dependencyGraph.ExplicitlyReferencedComponentIds); + } + } + + var explicitlyReferencedComponents = uniqueComponents + .Where(component => explicitComponentIds.Contains(component.Component.Id)); + if (configuration.EnablePackageMetadataParsing?.Value == true) { if (uniqueComponents.Any()) @@ -162,7 +177,7 @@ async Task Scan(string path) } // Converts every ScannedComponent into an ExtendedScannedComponent and attempts to add license information before writing to the channel. - foreach (var scannedComponent in uniqueComponents) + foreach (var scannedComponent in explicitlyReferencedComponents) { var componentName = scannedComponent.Component.PackageUrl?.Name; var componentVersion = scannedComponent.Component.PackageUrl?.Version; diff --git a/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs b/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs index 19add7a0d..4b9586df3 100644 --- a/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs +++ b/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs @@ -67,21 +67,22 @@ internal async Task GenerateJson( var generationResult = manifestGeneratorProvider.Get(sbomConfig.ManifestInfo).GenerateJsonDocument(packageInfo); - var recordedAnyDependencies = false; + //var recordedAnyDependencies = false; - if (generationResult?.ResultMetadata?.DependOn != null) - { - foreach (var dependency in generationResult?.ResultMetadata?.DependOn) - { - sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, dependency); - recordedAnyDependencies = true; - } - } + //if (generationResult?.ResultMetadata?.DependOn != null) + //{ + // foreach (var dependency in generationResult?.ResultMetadata?.DependOn) + // { + // sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, dependency); + // recordedAnyDependencies = true; + // } + //} - if (!recordedAnyDependencies) - { - sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, null); - } + //if (!recordedAnyDependencies) + //{ + //} + + sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, null); await result.Writer.WriteAsync((generationResult?.Document, sbomConfig.JsonSerializer)); } diff --git a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs index 41a66d626..6dd1995fa 100644 --- a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs +++ b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs @@ -177,6 +177,27 @@ public void E2E_CompareSPDX22AndSPDX30Manifests_ComparisonAndDeterminismSucceeds Assert.AreEqual(SbomEqualityComparisonResult.Equal, areEqual, "The SPDX 2.2 and SPDX 3.0 manifests should be equivalent."); } + [TestMethod] + public void E2E_GenerateManifest_GeneratesNoTransitivePackagesAndDeps_ReturnsZeroExitCode() + { + if (!IsWindows) + { + Assert.Inconclusive("This test is not (yet) supported on non-Windows platforms."); + return; + } + + var testFolderPath = CreateTestFolder(); + GenerateManifestAndValidateSuccess(testFolderPath); + + var originalManifestFolderPath = AppendFullManifestFolderPath(testFolderPath); + var originalManifestFilePath = Path.Combine(AppendFullManifestFolderPath(testFolderPath), ManifestFileName); + + var jsonElement = ReadJsonFile(originalManifestFilePath); + var packages = jsonElement.GetProperty("packages"); + var relationships = jsonElement.GetProperty("relationships"); + Assert.AreEqual(relationships.GetArrayLength(), packages.GetArrayLength() + 1, "The number of relationships should equal the number of packages + 1, indicating only direct package dependencies are recorded. 1 is added because there is a default DESCRIBES relationship."); + } + [TestMethod] public void E2E_GenerateAndRedactManifest_RedactedFileIsSmaller_ReturnsZeroExitCode() { From edda718b68127bd8ad63817da8add2543a30869f Mon Sep 17 00:00:00 2001 From: ppandrate Date: Fri, 26 Sep 2025 12:28:37 -0700 Subject: [PATCH 2/9] update unit test + casting scanresult since it affects UTs --- .../Executors/ComponentDetectionBaseWalker.cs | 19 ++++++++++--------- .../Executors/PackageInfoJsonWriterTests.cs | 11 ++--------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs b/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs index 0567d5686..21c35cb04 100644 --- a/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs +++ b/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs @@ -113,7 +113,7 @@ async Task Scan(string path) var scanSettings = cliArgumentBuilder.BuildScanSettingsFromParsedArgs(cmdLineParams); - var scanResult = await componentDetector.ScanAsync(scanSettings) as DefaultGraphScanResult; + var scanResult = await componentDetector.ScanAsync(scanSettings); if (scanResult.ResultCode != ProcessingResultCode.Success) { @@ -123,20 +123,21 @@ async Task Scan(string path) var uniqueComponents = FilterScannedComponents(scanResult); - // Collect all explicitly referenced component IDs from all dependency graphs - var explicitComponentIds = new HashSet(); - - if (scanResult.DependencyGraphs != null) + var defaultGraphScanResult = scanResult as DefaultGraphScanResult; + if (defaultGraphScanResult != null && defaultGraphScanResult.DependencyGraphs != null) { - foreach (var dependencyGraphPair in scanResult.DependencyGraphs) + // Collect all explicitly referenced component IDs from all dependency graphs + var explicitComponentIds = new HashSet(); + + foreach (var dependencyGraphPair in defaultGraphScanResult.DependencyGraphs) { var dependencyGraph = dependencyGraphPair.Value; explicitComponentIds.UnionWith(dependencyGraph.ExplicitlyReferencedComponentIds); } - } - var explicitlyReferencedComponents = uniqueComponents + uniqueComponents = uniqueComponents .Where(component => explicitComponentIds.Contains(component.Component.Id)); + } if (configuration.EnablePackageMetadataParsing?.Value == true) { @@ -177,7 +178,7 @@ async Task Scan(string path) } // Converts every ScannedComponent into an ExtendedScannedComponent and attempts to add license information before writing to the channel. - foreach (var scannedComponent in explicitlyReferencedComponents) + foreach (var scannedComponent in uniqueComponents) { var componentName = scannedComponent.Component.PackageUrl?.Name; var componentVersion = scannedComponent.Component.PackageUrl?.Version; diff --git a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs index de564c238..ead3828bd 100644 --- a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs +++ b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs @@ -77,7 +77,7 @@ public void AfterEach() [TestMethod] [DataRow(null, true)] [DataRow(new string[0], true)] - [DataRow(new[] { "a", "b", "c" }, false)] + [DataRow(new[] { "a", "b", "c" }, true)] public async Task GenerateJson_RecordsExpectedDependencies(string[] testCase, bool expectNullDependency) { var sbomConfigs = new[] { sbomConfigMock.Object }; @@ -93,16 +93,9 @@ public async Task GenerateJson_RecordsExpectedDependencies(string[] testCase, bo if (testCase is not null) { generationResult.ResultMetadata.DependOn = testCase.ToList(); - foreach (var test in testCase) - { - sbomPackageDetailsRecorderMock.Setup(m => m.RecordPackageId(TestEntityId, test)); - } } - if (expectNullDependency) - { - sbomPackageDetailsRecorderMock.Setup(m => m.RecordPackageId(TestEntityId, null)); - } + sbomPackageDetailsRecorderMock.Setup(m => m.RecordPackageId(TestEntityId, null)); await testSubject.GenerateJson(sbomConfigs, packageInfo, resultChannel, errorsChannel); } From 8991de218b18af07550b5de99449fe0c498548a1 Mon Sep 17 00:00:00 2001 From: ppandrate Date: Fri, 26 Sep 2025 13:06:41 -0700 Subject: [PATCH 3/9] fix e2e test --- test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs index 02516cdbe..420129775 100644 --- a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs +++ b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs @@ -195,7 +195,8 @@ public void E2E_GenerateManifest_GeneratesNoTransitivePackagesAndDeps_ReturnsZer var jsonElement = ReadJsonFile(originalManifestFilePath); var packages = jsonElement.GetProperty("packages"); var relationships = jsonElement.GetProperty("relationships"); - Assert.AreEqual(relationships.GetArrayLength(), packages.GetArrayLength() + 1, "The number of relationships should equal the number of packages + 1, indicating only direct package dependencies are recorded. 1 is added because there is a default DESCRIBES relationship."); + var expectedRelationshipCount = packages.GetArrayLength() + 1; + Assert.AreEqual(expectedRelationshipCount, relationships.GetArrayLength(), "The number of relationships should equal the number of packages + 1, indicating only direct package dependencies are recorded. 1 is added because there is a default DESCRIBES relationship."); } [TestMethod] From 311426e6680a7ae7faaa7913fadea90d0d1d18bd Mon Sep 17 00:00:00 2001 From: ppandrate Date: Mon, 29 Sep 2025 17:33:36 -0700 Subject: [PATCH 4/9] fix component detection dependOn value, restore changes to jsonwriter, fix e2e test --- .../CargoComponentExtensions.cs | 3 +-- .../MavenComponentExtensions.cs | 3 +-- .../NpmComponentExtensions.cs | 3 +-- .../NuGetComponentExtensions.cs | 2 +- .../PipComponentExtensions.cs | 3 +-- .../PodComponentExtensions.cs | 3 +-- .../RubyGemsComponentExtensions.cs | 3 +-- .../Executors/PackageInfoJsonWriter.cs | 27 +++++++++---------- .../IntegrationTests.cs | 3 +-- 9 files changed, 21 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/CargoComponentExtensions.cs b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/CargoComponentExtensions.cs index d6396c08b..ea115d420 100644 --- a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/CargoComponentExtensions.cs +++ b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/CargoComponentExtensions.cs @@ -3,7 +3,6 @@ namespace Microsoft.Sbom.Adapters.ComponentDetection; -using System.Linq; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.Sbom.Contracts; @@ -30,6 +29,6 @@ internal static class CargoComponentExtensions }, FilesAnalyzed = false, Type = "cargo", - DependOn = component.AncestralReferrers?.Select(r => r.Id).ToList(), + DependOn = null }; } diff --git a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/MavenComponentExtensions.cs b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/MavenComponentExtensions.cs index 24ba532d3..42ca7944f 100644 --- a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/MavenComponentExtensions.cs +++ b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/MavenComponentExtensions.cs @@ -3,7 +3,6 @@ namespace Microsoft.Sbom.Adapters.ComponentDetection; -using System.Linq; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.Sbom.Contracts; @@ -31,6 +30,6 @@ internal static class MavenComponentExtensions Declared = component.LicenseDeclared, }, Type = "maven", - DependOn = component.AncestralReferrers?.Select(r => r.Id).ToList(), + DependOn = null }; } diff --git a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NpmComponentExtensions.cs b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NpmComponentExtensions.cs index 8a1398613..f1c769db6 100644 --- a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NpmComponentExtensions.cs +++ b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NpmComponentExtensions.cs @@ -4,7 +4,6 @@ namespace Microsoft.Sbom.Adapters.ComponentDetection; using System; -using System.Linq; using Microsoft.ComponentDetection.Contracts.Internal; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.Sbom.Contracts; @@ -40,7 +39,7 @@ internal static class NpmComponentExtensions }, FilesAnalyzed = false, Type = "npm", - DependOn = component.AncestralReferrers?.Select(r => r.Id).ToList(), + DependOn = null }; /// diff --git a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NuGetComponentExtensions.cs b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NuGetComponentExtensions.cs index e3a9266b7..f5520d30c 100644 --- a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NuGetComponentExtensions.cs +++ b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/NuGetComponentExtensions.cs @@ -32,6 +32,6 @@ internal static class NuGetComponentExtensions }, FilesAnalyzed = false, Type = "nuget", - DependOn = component.AncestralReferrers?.Select(r => r.Id).ToList(), + DependOn = null }; } diff --git a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PipComponentExtensions.cs b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PipComponentExtensions.cs index d30cb0de6..0d4f6795f 100644 --- a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PipComponentExtensions.cs +++ b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PipComponentExtensions.cs @@ -3,7 +3,6 @@ namespace Microsoft.Sbom.Adapters.ComponentDetection; -using System.Linq; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.Sbom.Contracts; @@ -30,6 +29,6 @@ internal static class PipComponentExtensions }, FilesAnalyzed = false, Type = "python", - DependOn = component.AncestralReferrers?.Select(r => r.Id).ToList(), + DependOn = null }; } diff --git a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PodComponentExtensions.cs b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PodComponentExtensions.cs index bef656520..36ceba0db 100644 --- a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PodComponentExtensions.cs +++ b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/PodComponentExtensions.cs @@ -3,7 +3,6 @@ namespace Microsoft.Sbom.Adapters.ComponentDetection; -using System.Linq; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.Sbom.Contracts; @@ -31,6 +30,6 @@ internal static class PodComponentExtensions }, FilesAnalyzed = false, Type = "pod", - DependOn = component.AncestralReferrers?.Select(r => r.Id).ToList(), + DependOn = null }; } diff --git a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/RubyGemsComponentExtensions.cs b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/RubyGemsComponentExtensions.cs index 2e1e67aa7..6d9dacdf4 100644 --- a/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/RubyGemsComponentExtensions.cs +++ b/src/Microsoft.Sbom.Adapters/Adapters/ComponentDetection/RubyGemsComponentExtensions.cs @@ -3,7 +3,6 @@ namespace Microsoft.Sbom.Adapters.ComponentDetection; -using System.Linq; using Microsoft.ComponentDetection.Contracts.TypedComponent; using Microsoft.Sbom.Contracts; @@ -33,6 +32,6 @@ internal static class RubyGemsComponentExtensions }, FilesAnalyzed = false, Type = "ruby", - DependOn = component.AncestralReferrers?.Select(r => r.Id).ToList(), + DependOn = null }; } diff --git a/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs b/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs index 4b9586df3..19add7a0d 100644 --- a/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs +++ b/src/Microsoft.Sbom.Api/Executors/PackageInfoJsonWriter.cs @@ -67,22 +67,21 @@ internal async Task GenerateJson( var generationResult = manifestGeneratorProvider.Get(sbomConfig.ManifestInfo).GenerateJsonDocument(packageInfo); - //var recordedAnyDependencies = false; + var recordedAnyDependencies = false; - //if (generationResult?.ResultMetadata?.DependOn != null) - //{ - // foreach (var dependency in generationResult?.ResultMetadata?.DependOn) - // { - // sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, dependency); - // recordedAnyDependencies = true; - // } - //} + if (generationResult?.ResultMetadata?.DependOn != null) + { + foreach (var dependency in generationResult?.ResultMetadata?.DependOn) + { + sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, dependency); + recordedAnyDependencies = true; + } + } - //if (!recordedAnyDependencies) - //{ - //} - - sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, null); + if (!recordedAnyDependencies) + { + sbomConfig.Recorder.RecordPackageId(generationResult?.ResultMetadata?.EntityId, null); + } await result.Writer.WriteAsync((generationResult?.Document, sbomConfig.JsonSerializer)); } diff --git a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs index 420129775..1e690376a 100644 --- a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs +++ b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs @@ -195,8 +195,7 @@ public void E2E_GenerateManifest_GeneratesNoTransitivePackagesAndDeps_ReturnsZer var jsonElement = ReadJsonFile(originalManifestFilePath); var packages = jsonElement.GetProperty("packages"); var relationships = jsonElement.GetProperty("relationships"); - var expectedRelationshipCount = packages.GetArrayLength() + 1; - Assert.AreEqual(expectedRelationshipCount, relationships.GetArrayLength(), "The number of relationships should equal the number of packages + 1, indicating only direct package dependencies are recorded. 1 is added because there is a default DESCRIBES relationship."); + Assert.AreEqual(packages.GetArrayLength(), relationships.GetArrayLength(), "The number of relationships should equal the number of packages + 1, indicating only direct package dependencies are recorded. 1 is added because there is a default DESCRIBES relationship."); } [TestMethod] From b72db125c56faf0c3b1a052766127a46314ad27d Mon Sep 17 00:00:00 2001 From: ppandrate Date: Mon, 29 Sep 2025 17:45:50 -0700 Subject: [PATCH 5/9] refactor to private method --- .../Executors/ComponentDetectionBaseWalker.cs | 42 ++++++++++++------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs b/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs index 21c35cb04..40f1d6e84 100644 --- a/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs +++ b/src/Microsoft.Sbom.Api/Executors/ComponentDetectionBaseWalker.cs @@ -123,21 +123,7 @@ async Task Scan(string path) var uniqueComponents = FilterScannedComponents(scanResult); - var defaultGraphScanResult = scanResult as DefaultGraphScanResult; - if (defaultGraphScanResult != null && defaultGraphScanResult.DependencyGraphs != null) - { - // Collect all explicitly referenced component IDs from all dependency graphs - var explicitComponentIds = new HashSet(); - - foreach (var dependencyGraphPair in defaultGraphScanResult.DependencyGraphs) - { - var dependencyGraph = dependencyGraphPair.Value; - explicitComponentIds.UnionWith(dependencyGraph.ExplicitlyReferencedComponentIds); - } - - uniqueComponents = uniqueComponents - .Where(component => explicitComponentIds.Contains(component.Component.Id)); - } + uniqueComponents = ProcessDependencyGraphs(scanResult, uniqueComponents); if (configuration.EnablePackageMetadataParsing?.Value == true) { @@ -233,4 +219,30 @@ async Task Scan(string path) } protected abstract IEnumerable FilterScannedComponents(ScanResult result); + + /// + /// Processes dependency graphs to filter components based on explicitly referenced component IDs. + /// + /// The scan result from component detection. + /// The collection of unique components to filter. + /// Filtered collection of components that are explicitly referenced in dependency graphs. + private IEnumerable ProcessDependencyGraphs(ScanResult scanResult, IEnumerable uniqueComponents) + { + var defaultGraphScanResult = scanResult as DefaultGraphScanResult; + if (defaultGraphScanResult != null && defaultGraphScanResult.DependencyGraphs != null) + { + // Collect all explicitly referenced component IDs from all dependency graphs + var explicitComponentIds = new HashSet(); + + foreach (var dependencyGraphPair in defaultGraphScanResult.DependencyGraphs) + { + var dependencyGraph = dependencyGraphPair.Value; + explicitComponentIds.UnionWith(dependencyGraph.ExplicitlyReferencedComponentIds); + } + + return uniqueComponents.Where(component => explicitComponentIds.Contains(component.Component.Id)); + } + + return uniqueComponents; + } } From c7892b8c084837aa16903bae1210ae08280ae1c1 Mon Sep 17 00:00:00 2001 From: ppandrate Date: Tue, 30 Sep 2025 10:01:09 -0700 Subject: [PATCH 6/9] revert change to ut --- .../Executors/PackageInfoJsonWriterTests.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs index ead3828bd..1c635304c 100644 --- a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs +++ b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs @@ -77,7 +77,7 @@ public void AfterEach() [TestMethod] [DataRow(null, true)] [DataRow(new string[0], true)] - [DataRow(new[] { "a", "b", "c" }, true)] + [DataRow(new[] { "a", "b", "c" }, false)] public async Task GenerateJson_RecordsExpectedDependencies(string[] testCase, bool expectNullDependency) { var sbomConfigs = new[] { sbomConfigMock.Object }; @@ -89,13 +89,19 @@ public async Task GenerateJson_RecordsExpectedDependencies(string[] testCase, bo }; var resultChannel = Channel.CreateUnbounded(); var errorsChannel = Channel.CreateUnbounded(); - if (testCase is not null) { generationResult.ResultMetadata.DependOn = testCase.ToList(); + foreach (var test in testCase) + { + sbomPackageDetailsRecorderMock.Setup(m => m.RecordPackageId(TestEntityId, test)); + } } - sbomPackageDetailsRecorderMock.Setup(m => m.RecordPackageId(TestEntityId, null)); + if (expectNullDependency) + { + sbomPackageDetailsRecorderMock.Setup(m => m.RecordPackageId(TestEntityId, null)); + } await testSubject.GenerateJson(sbomConfigs, packageInfo, resultChannel, errorsChannel); } From 96184117e644f276f6c1eceb58db5ef7f224ebe2 Mon Sep 17 00:00:00 2001 From: ppandrate Date: Tue, 30 Sep 2025 10:10:10 -0700 Subject: [PATCH 7/9] revert whitespace change --- .../Executors/PackageInfoJsonWriterTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs index 1c635304c..02bdecdd5 100644 --- a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs +++ b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs @@ -87,6 +87,7 @@ public async Task GenerateJson_RecordsExpectedDependencies(string[] testCase, bo PackageVersion = "1.0.0", PackageUrl = "pkg:example/testpackage@1.0.0" }; + var resultChannel = Channel.CreateUnbounded(); var errorsChannel = Channel.CreateUnbounded(); if (testCase is not null) From f77db3246cba104d3c3fde18b341a5bb4f563e4b Mon Sep 17 00:00:00 2001 From: ppandrate Date: Tue, 30 Sep 2025 10:11:50 -0700 Subject: [PATCH 8/9] revert whitespace change --- .../Executors/PackageInfoJsonWriterTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs index 02bdecdd5..de564c238 100644 --- a/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs +++ b/test/Microsoft.Sbom.Api.Tests/Executors/PackageInfoJsonWriterTests.cs @@ -87,9 +87,9 @@ public async Task GenerateJson_RecordsExpectedDependencies(string[] testCase, bo PackageVersion = "1.0.0", PackageUrl = "pkg:example/testpackage@1.0.0" }; - var resultChannel = Channel.CreateUnbounded(); var errorsChannel = Channel.CreateUnbounded(); + if (testCase is not null) { generationResult.ResultMetadata.DependOn = testCase.ToList(); From be5dca1853fc044625a4c815bc3e741e87e5dba8 Mon Sep 17 00:00:00 2001 From: ppandrate Date: Tue, 30 Sep 2025 12:05:35 -0700 Subject: [PATCH 9/9] modify comment --- test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs index 1e690376a..a323d3cbe 100644 --- a/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs +++ b/test/Microsoft.Sbom.Tool.Tests/IntegrationTests.cs @@ -195,7 +195,7 @@ public void E2E_GenerateManifest_GeneratesNoTransitivePackagesAndDeps_ReturnsZer var jsonElement = ReadJsonFile(originalManifestFilePath); var packages = jsonElement.GetProperty("packages"); var relationships = jsonElement.GetProperty("relationships"); - Assert.AreEqual(packages.GetArrayLength(), relationships.GetArrayLength(), "The number of relationships should equal the number of packages + 1, indicating only direct package dependencies are recorded. 1 is added because there is a default DESCRIBES relationship."); + Assert.AreEqual(packages.GetArrayLength(), relationships.GetArrayLength(), "The number of relationships should equal the number of packages."); } [TestMethod]