tests fixes and sprints work
This commit is contained in:
@@ -0,0 +1,670 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using FluentAssertions;
|
||||
using StellaOps.Concelier.SbomIntegration.Models;
|
||||
using StellaOps.Scanner.Reachability.Dependencies;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
using static StellaOps.Scanner.Reachability.Tests.DependencyTestData;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Tests;
|
||||
|
||||
public sealed class DependencyGraphBuilderTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_UsesMetadataRootAndDependencies()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("app", type: "application"),
|
||||
Component("lib-a"),
|
||||
Component("lib-b")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("app", ["lib-a"], DependencyScope.Runtime),
|
||||
Dependency("lib-a", ["lib-b"], DependencyScope.Optional)
|
||||
],
|
||||
rootRef: "app");
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
graph.Nodes.Should().Contain(new[] { "app", "lib-a", "lib-b" });
|
||||
graph.Edges.Should().ContainKey("app");
|
||||
graph.Edges["app"].Should().ContainSingle(edge =>
|
||||
edge.From == "app" &&
|
||||
edge.To == "lib-a" &&
|
||||
edge.Scope == DependencyScope.Runtime);
|
||||
graph.Edges["lib-a"].Should().ContainSingle(edge =>
|
||||
edge.From == "lib-a" &&
|
||||
edge.To == "lib-b" &&
|
||||
edge.Scope == DependencyScope.Optional);
|
||||
graph.Roots.Should().ContainSingle().Which.Should().Be("app");
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Linear chain test
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_LinearChain_CreatesCorrectGraph()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("a", type: "application"),
|
||||
Component("b"),
|
||||
Component("c"),
|
||||
Component("d")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("a", ["b"], DependencyScope.Runtime),
|
||||
Dependency("b", ["c"], DependencyScope.Runtime),
|
||||
Dependency("c", ["d"], DependencyScope.Runtime)
|
||||
],
|
||||
rootRef: "a");
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
graph.Nodes.Should().HaveCount(4);
|
||||
graph.Edges["a"].Should().ContainSingle(e => e.To == "b");
|
||||
graph.Edges["b"].Should().ContainSingle(e => e.To == "c");
|
||||
graph.Edges["c"].Should().ContainSingle(e => e.To == "d");
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Diamond dependency test
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_DiamondDependency_CreatesCorrectGraph()
|
||||
{
|
||||
// Diamond: A -> B -> D
|
||||
// A -> C -> D
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("a", type: "application"),
|
||||
Component("b"),
|
||||
Component("c"),
|
||||
Component("d")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("a", ["b", "c"], DependencyScope.Runtime),
|
||||
Dependency("b", ["d"], DependencyScope.Runtime),
|
||||
Dependency("c", ["d"], DependencyScope.Runtime)
|
||||
],
|
||||
rootRef: "a");
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
graph.Nodes.Should().HaveCount(4);
|
||||
graph.Edges["a"].Should().HaveCount(2);
|
||||
graph.Edges["b"].Should().ContainSingle(e => e.To == "d");
|
||||
graph.Edges["c"].Should().ContainSingle(e => e.To == "d");
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Circular dependency test
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_CircularDependency_HandlesCorrectly()
|
||||
{
|
||||
// Circular: A -> B -> C -> A
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("a", type: "application"),
|
||||
Component("b"),
|
||||
Component("c")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("a", ["b"], DependencyScope.Runtime),
|
||||
Dependency("b", ["c"], DependencyScope.Runtime),
|
||||
Dependency("c", ["a"], DependencyScope.Runtime)
|
||||
],
|
||||
rootRef: "a");
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
graph.Nodes.Should().HaveCount(3);
|
||||
graph.Edges["a"].Should().ContainSingle(e => e.To == "b");
|
||||
graph.Edges["b"].Should().ContainSingle(e => e.To == "c");
|
||||
graph.Edges["c"].Should().ContainSingle(e => e.To == "a");
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Empty SBOM test
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_EmptySbom_ReturnsEmptyGraph()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components: [],
|
||||
dependencies: []);
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
graph.Nodes.Should().BeEmpty();
|
||||
graph.Edges.Should().BeEmpty();
|
||||
graph.Roots.Should().BeEmpty();
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Multiple roots test
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_MultipleRoots_DetectsAllRoots()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("app-1", type: "application"),
|
||||
Component("app-2", type: "application"),
|
||||
Component("shared-lib")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("app-1", ["shared-lib"], DependencyScope.Runtime),
|
||||
Dependency("app-2", ["shared-lib"], DependencyScope.Runtime)
|
||||
]);
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
graph.Roots.Should().BeEquivalentTo(["app-1", "app-2"]);
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Missing dependency target
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_MissingDependencyTarget_HandlesGracefully()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("app", type: "application")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("app", ["missing-lib"], DependencyScope.Runtime)
|
||||
],
|
||||
rootRef: "app");
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
// Should still build graph even with missing target
|
||||
graph.Nodes.Should().Contain("app");
|
||||
graph.Edges["app"].Should().ContainSingle(e => e.To == "missing-lib");
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Mixed scope dependencies
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Build_MixedScopes_PreservesAllScopes()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("app", type: "application"),
|
||||
Component("runtime-lib"),
|
||||
Component("dev-lib"),
|
||||
Component("test-lib"),
|
||||
Component("optional-lib")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("app", ["runtime-lib"], DependencyScope.Runtime),
|
||||
Dependency("app", ["dev-lib"], DependencyScope.Development),
|
||||
Dependency("app", ["test-lib"], DependencyScope.Test),
|
||||
Dependency("app", ["optional-lib"], DependencyScope.Optional)
|
||||
],
|
||||
rootRef: "app");
|
||||
|
||||
var builder = new DependencyGraphBuilder();
|
||||
var graph = builder.Build(sbom);
|
||||
|
||||
var edges = graph.Edges["app"];
|
||||
edges.Should().HaveCount(4);
|
||||
edges.Should().Contain(e => e.To == "runtime-lib" && e.Scope == DependencyScope.Runtime);
|
||||
edges.Should().Contain(e => e.To == "dev-lib" && e.Scope == DependencyScope.Development);
|
||||
edges.Should().Contain(e => e.To == "test-lib" && e.Scope == DependencyScope.Test);
|
||||
edges.Should().Contain(e => e.To == "optional-lib" && e.Scope == DependencyScope.Optional);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EntryPointDetectorTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectEntryPoints_IncludesPolicyAndSbomSignals()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("root", type: "application"),
|
||||
Component("worker", type: "application")
|
||||
],
|
||||
dependencies: [],
|
||||
rootRef: "root");
|
||||
|
||||
var policy = new ReachabilityPolicy
|
||||
{
|
||||
EntryPoints = new ReachabilityEntryPointPolicy
|
||||
{
|
||||
Additional = ["extra-entry"]
|
||||
}
|
||||
};
|
||||
|
||||
var detector = new EntryPointDetector();
|
||||
|
||||
var entryPoints = detector.DetectEntryPoints(sbom, policy);
|
||||
|
||||
entryPoints.Should().Contain(new[] { "extra-entry", "root", "worker" });
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectEntryPoints_FallsBackToAllComponents()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("lib-a", type: "library"),
|
||||
Component("lib-b", type: "library")
|
||||
],
|
||||
dependencies: []);
|
||||
|
||||
var detector = new EntryPointDetector();
|
||||
|
||||
var entryPoints = detector.DetectEntryPoints(sbom);
|
||||
|
||||
entryPoints.Should().BeEquivalentTo(new[] { "lib-a", "lib-b" });
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Policy disables SBOM detection
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectEntryPoints_PolicyDisablesSbomDetection_OnlyUsesAdditional()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("app", type: "application"),
|
||||
Component("lib")
|
||||
],
|
||||
dependencies: [],
|
||||
rootRef: "app");
|
||||
|
||||
var policy = new ReachabilityPolicy
|
||||
{
|
||||
EntryPoints = new ReachabilityEntryPointPolicy
|
||||
{
|
||||
DetectFromSbom = false,
|
||||
Additional = ["custom-entry"]
|
||||
}
|
||||
};
|
||||
|
||||
var detector = new EntryPointDetector();
|
||||
|
||||
var entryPoints = detector.DetectEntryPoints(sbom, policy);
|
||||
|
||||
entryPoints.Should().ContainSingle("custom-entry");
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Empty SBOM entry points
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectEntryPoints_EmptySbom_ReturnsEmpty()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components: [],
|
||||
dependencies: []);
|
||||
|
||||
var detector = new EntryPointDetector();
|
||||
|
||||
var entryPoints = detector.DetectEntryPoints(sbom);
|
||||
|
||||
entryPoints.Should().BeEmpty();
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Entry points from container type
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void DetectEntryPoints_ContainerComponent_TreatedAsEntryPoint()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("my-container", type: "container"),
|
||||
Component("lib")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("my-container", ["lib"], DependencyScope.Runtime)
|
||||
]);
|
||||
|
||||
var detector = new EntryPointDetector();
|
||||
|
||||
var entryPoints = detector.DetectEntryPoints(sbom);
|
||||
|
||||
entryPoints.Should().Contain("my-container");
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StaticReachabilityAnalyzerTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_RespectsScopeHandling()
|
||||
{
|
||||
var graph = new DependencyGraph
|
||||
{
|
||||
Nodes = ["app", "runtime-lib", "dev-lib", "optional-lib"],
|
||||
Edges = new Dictionary<string, ImmutableArray<DependencyEdge>>
|
||||
{
|
||||
["app"] =
|
||||
[
|
||||
new DependencyEdge { From = "app", To = "runtime-lib", Scope = DependencyScope.Runtime },
|
||||
new DependencyEdge { From = "app", To = "optional-lib", Scope = DependencyScope.Optional }
|
||||
],
|
||||
["runtime-lib"] =
|
||||
[
|
||||
new DependencyEdge { From = "runtime-lib", To = "dev-lib", Scope = DependencyScope.Development }
|
||||
]
|
||||
}.ToImmutableDictionary(StringComparer.Ordinal)
|
||||
};
|
||||
|
||||
var policy = new ReachabilityPolicy
|
||||
{
|
||||
ScopeHandling = new ReachabilityScopePolicy
|
||||
{
|
||||
IncludeRuntime = true,
|
||||
IncludeOptional = OptionalDependencyHandling.AsPotentiallyReachable,
|
||||
IncludeDevelopment = false,
|
||||
IncludeTest = false
|
||||
}
|
||||
};
|
||||
|
||||
var analyzer = new StaticReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, ["app"], policy);
|
||||
|
||||
report.ComponentReachability["app"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["runtime-lib"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["optional-lib"].Should().Be(ReachabilityStatus.PotentiallyReachable);
|
||||
report.ComponentReachability["dev-lib"].Should().Be(ReachabilityStatus.Unreachable);
|
||||
|
||||
report.Findings.Should().Contain(finding =>
|
||||
finding.ComponentRef == "optional-lib" &&
|
||||
finding.Path.SequenceEqual(new[] { "app", "optional-lib" }));
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_WithoutEntryPoints_MarksUnknown()
|
||||
{
|
||||
var graph = new DependencyGraph
|
||||
{
|
||||
Nodes = ["lib-a", "lib-b"]
|
||||
};
|
||||
|
||||
var analyzer = new StaticReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, [], null);
|
||||
|
||||
report.ComponentReachability["lib-a"].Should().Be(ReachabilityStatus.Unknown);
|
||||
report.ComponentReachability["lib-b"].Should().Be(ReachabilityStatus.Unknown);
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Circular dependency traversal
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_CircularDependency_MarksAllReachable()
|
||||
{
|
||||
// Circular: A -> B -> C -> A
|
||||
var graph = new DependencyGraph
|
||||
{
|
||||
Nodes = ["a", "b", "c"],
|
||||
Edges = new Dictionary<string, ImmutableArray<DependencyEdge>>
|
||||
{
|
||||
["a"] = [new DependencyEdge { From = "a", To = "b", Scope = DependencyScope.Runtime }],
|
||||
["b"] = [new DependencyEdge { From = "b", To = "c", Scope = DependencyScope.Runtime }],
|
||||
["c"] = [new DependencyEdge { From = "c", To = "a", Scope = DependencyScope.Runtime }]
|
||||
}.ToImmutableDictionary(StringComparer.Ordinal),
|
||||
Roots = ["a"]
|
||||
};
|
||||
|
||||
var analyzer = new StaticReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, ["a"], null);
|
||||
|
||||
report.ComponentReachability["a"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["b"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["c"].Should().Be(ReachabilityStatus.Reachable);
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Multiple entry points
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_MultipleEntryPoints_MarksAllReachablePaths()
|
||||
{
|
||||
// Entry1 -> A, Entry2 -> B, A and B are independent
|
||||
var graph = new DependencyGraph
|
||||
{
|
||||
Nodes = ["entry1", "entry2", "a", "b", "orphan"],
|
||||
Edges = new Dictionary<string, ImmutableArray<DependencyEdge>>
|
||||
{
|
||||
["entry1"] = [new DependencyEdge { From = "entry1", To = "a", Scope = DependencyScope.Runtime }],
|
||||
["entry2"] = [new DependencyEdge { From = "entry2", To = "b", Scope = DependencyScope.Runtime }]
|
||||
}.ToImmutableDictionary(StringComparer.Ordinal)
|
||||
};
|
||||
|
||||
var analyzer = new StaticReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, ["entry1", "entry2"], null);
|
||||
|
||||
report.ComponentReachability["entry1"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["entry2"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["a"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["b"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["orphan"].Should().Be(ReachabilityStatus.Unreachable);
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Test scope handling
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_TestScopeExcluded_MarksUnreachable()
|
||||
{
|
||||
var graph = new DependencyGraph
|
||||
{
|
||||
Nodes = ["app", "runtime-lib", "test-lib"],
|
||||
Edges = new Dictionary<string, ImmutableArray<DependencyEdge>>
|
||||
{
|
||||
["app"] =
|
||||
[
|
||||
new DependencyEdge { From = "app", To = "runtime-lib", Scope = DependencyScope.Runtime },
|
||||
new DependencyEdge { From = "app", To = "test-lib", Scope = DependencyScope.Test }
|
||||
]
|
||||
}.ToImmutableDictionary(StringComparer.Ordinal)
|
||||
};
|
||||
|
||||
var policy = new ReachabilityPolicy
|
||||
{
|
||||
ScopeHandling = new ReachabilityScopePolicy
|
||||
{
|
||||
IncludeRuntime = true,
|
||||
IncludeTest = false
|
||||
}
|
||||
};
|
||||
|
||||
var analyzer = new StaticReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, ["app"], policy);
|
||||
|
||||
report.ComponentReachability["app"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["runtime-lib"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["test-lib"].Should().Be(ReachabilityStatus.Unreachable);
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260119_022 TASK-022-011 - Deep transitive dependencies
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_DeepTransitiveDependencies_MarksAllReachable()
|
||||
{
|
||||
// 5-level deep: app -> a -> b -> c -> d -> e
|
||||
var graph = new DependencyGraph
|
||||
{
|
||||
Nodes = ["app", "a", "b", "c", "d", "e"],
|
||||
Edges = new Dictionary<string, ImmutableArray<DependencyEdge>>
|
||||
{
|
||||
["app"] = [new DependencyEdge { From = "app", To = "a", Scope = DependencyScope.Runtime }],
|
||||
["a"] = [new DependencyEdge { From = "a", To = "b", Scope = DependencyScope.Runtime }],
|
||||
["b"] = [new DependencyEdge { From = "b", To = "c", Scope = DependencyScope.Runtime }],
|
||||
["c"] = [new DependencyEdge { From = "c", To = "d", Scope = DependencyScope.Runtime }],
|
||||
["d"] = [new DependencyEdge { From = "d", To = "e", Scope = DependencyScope.Runtime }]
|
||||
}.ToImmutableDictionary(StringComparer.Ordinal)
|
||||
};
|
||||
|
||||
var analyzer = new StaticReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, ["app"], null);
|
||||
|
||||
foreach (var node in graph.Nodes)
|
||||
{
|
||||
report.ComponentReachability[node].Should().Be(ReachabilityStatus.Reachable,
|
||||
because: $"node {node} should be reachable from app");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class ConditionalReachabilityAnalyzerTests
|
||||
{
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_MarksConditionalDependenciesAndConditions()
|
||||
{
|
||||
var properties = ImmutableDictionary<string, string>.Empty
|
||||
.Add("stellaops.reachability.condition", "feature:beta");
|
||||
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("app", type: "application"),
|
||||
Component("optional-lib", scope: ComponentScope.Optional),
|
||||
Component("flagged-lib", properties: properties)
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("app", ["optional-lib"], DependencyScope.Optional),
|
||||
Dependency("optional-lib", ["flagged-lib"], DependencyScope.Runtime)
|
||||
],
|
||||
rootRef: "app");
|
||||
|
||||
var graph = new DependencyGraphBuilder().Build(sbom);
|
||||
var entryPoints = new EntryPointDetector().DetectEntryPoints(sbom);
|
||||
|
||||
var analyzer = new ConditionalReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, sbom, entryPoints);
|
||||
|
||||
report.ComponentReachability["app"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.ComponentReachability["optional-lib"].Should()
|
||||
.Be(ReachabilityStatus.PotentiallyReachable);
|
||||
report.ComponentReachability["flagged-lib"].Should()
|
||||
.Be(ReachabilityStatus.PotentiallyReachable);
|
||||
|
||||
report.Findings.Single(finding => finding.ComponentRef == "flagged-lib")
|
||||
.Conditions.Should().Equal(
|
||||
"component.scope.optional",
|
||||
"dependency.scope.optional",
|
||||
"feature:beta");
|
||||
}
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
[Fact]
|
||||
public void Analyze_PromotesToReachableWhenUnconditionalPathExists()
|
||||
{
|
||||
var sbom = BuildSbom(
|
||||
components:
|
||||
[
|
||||
Component("app", type: "application"),
|
||||
Component("lib-a")
|
||||
],
|
||||
dependencies:
|
||||
[
|
||||
Dependency("app", ["lib-a"], DependencyScope.Optional),
|
||||
Dependency("app", ["lib-a"], DependencyScope.Runtime)
|
||||
],
|
||||
rootRef: "app");
|
||||
|
||||
var graph = new DependencyGraphBuilder().Build(sbom);
|
||||
var entryPoints = new EntryPointDetector().DetectEntryPoints(sbom);
|
||||
|
||||
var analyzer = new ConditionalReachabilityAnalyzer();
|
||||
|
||||
var report = analyzer.Analyze(graph, sbom, entryPoints);
|
||||
|
||||
report.ComponentReachability["lib-a"].Should().Be(ReachabilityStatus.Reachable);
|
||||
report.Findings.Single(finding => finding.ComponentRef == "lib-a")
|
||||
.Conditions.Should().BeEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
internal static class DependencyTestData
|
||||
{
|
||||
public static ParsedSbom BuildSbom(
|
||||
ImmutableArray<ParsedComponent> components,
|
||||
ImmutableArray<ParsedDependency> dependencies,
|
||||
string? rootRef = null)
|
||||
{
|
||||
return new ParsedSbom
|
||||
{
|
||||
Format = "cyclonedx",
|
||||
SpecVersion = "1.7",
|
||||
SerialNumber = "urn:uuid:reachability-test",
|
||||
Components = components,
|
||||
Dependencies = dependencies,
|
||||
Metadata = new ParsedSbomMetadata
|
||||
{
|
||||
RootComponentRef = rootRef
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ParsedComponent Component(
|
||||
string bomRef,
|
||||
string? type = null,
|
||||
ComponentScope scope = ComponentScope.Required,
|
||||
ImmutableDictionary<string, string>? properties = null,
|
||||
string? purl = null)
|
||||
{
|
||||
return new ParsedComponent
|
||||
{
|
||||
BomRef = bomRef,
|
||||
Name = bomRef,
|
||||
Type = type,
|
||||
Scope = scope,
|
||||
Properties = properties ?? ImmutableDictionary<string, string>.Empty,
|
||||
Purl = purl
|
||||
};
|
||||
}
|
||||
|
||||
public static ParsedDependency Dependency(
|
||||
string source,
|
||||
ImmutableArray<string> dependsOn,
|
||||
DependencyScope scope)
|
||||
{
|
||||
return new ParsedDependency
|
||||
{
|
||||
SourceRef = source,
|
||||
DependsOn = dependsOn,
|
||||
Scope = scope
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user