up
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
This commit is contained in:
@@ -4,272 +4,272 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Globalization;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.Diff;
|
||||
using StellaOps.Scanner.Core.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Diff.Tests;
|
||||
|
||||
public sealed class ComponentDifferTests
|
||||
{
|
||||
[Fact]
|
||||
public void Compute_CapturesAddedRemovedAndChangedComponents()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:layer1", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:layer1",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/start.sh" }),
|
||||
evidence: new[] { ComponentEvidence.FromPath("/app/package-lock.json") }),
|
||||
CreateComponent("pkg:npm/b", version: "2.0.0", layer: "sha256:layer1", scope: "runtime"),
|
||||
}),
|
||||
LayerComponentFragment.Create("sha256:layer1b", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:layer1b",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/start.sh" })),
|
||||
CreateComponent("pkg:npm/d", version: "0.9.0", layer: "sha256:layer1b"),
|
||||
})
|
||||
};
|
||||
|
||||
var newFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:layer2", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.1.0",
|
||||
layer: "sha256:layer2",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/start.sh" }),
|
||||
evidence: new[] { ComponentEvidence.FromPath("/app/package-lock.json") }),
|
||||
}),
|
||||
LayerComponentFragment.Create("sha256:layer3", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/b",
|
||||
version: "2.0.0",
|
||||
layer: "sha256:layer3",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/init.sh" }),
|
||||
scope: "runtime"),
|
||||
CreateComponent("pkg:npm/c", version: "3.0.0", layer: "sha256:layer3"),
|
||||
})
|
||||
};
|
||||
|
||||
var oldGraph = ComponentGraphBuilder.Build(oldFragments);
|
||||
var newGraph = ComponentGraphBuilder.Build(newFragments);
|
||||
|
||||
var request = new ComponentDiffRequest
|
||||
{
|
||||
OldGraph = oldGraph,
|
||||
NewGraph = newGraph,
|
||||
GeneratedAt = new DateTimeOffset(2025, 10, 19, 10, 0, 0, TimeSpan.Zero),
|
||||
View = SbomView.Inventory,
|
||||
OldImageDigest = "sha256:old",
|
||||
NewImageDigest = "sha256:new",
|
||||
};
|
||||
|
||||
var differ = new ComponentDiffer();
|
||||
var document = differ.Compute(request);
|
||||
|
||||
Assert.Equal(SbomView.Inventory, document.View);
|
||||
Assert.Equal("sha256:old", document.OldImageDigest);
|
||||
Assert.Equal("sha256:new", document.NewImageDigest);
|
||||
Assert.Equal(1, document.Summary.Added);
|
||||
Assert.Equal(1, document.Summary.Removed);
|
||||
Assert.Equal(1, document.Summary.VersionChanged);
|
||||
Assert.Equal(1, document.Summary.MetadataChanged);
|
||||
|
||||
Assert.Equal(new[] { "sha256:layer2", "sha256:layer3", "sha256:layer1b" }, document.Layers.Select(layer => layer.LayerDigest));
|
||||
|
||||
var layerGroups = document.Layers.ToDictionary(layer => layer.LayerDigest);
|
||||
Assert.True(layerGroups.ContainsKey("sha256:layer2"), "Expected layer2 group present");
|
||||
Assert.True(layerGroups.ContainsKey("sha256:layer3"), "Expected layer3 group present");
|
||||
Assert.True(layerGroups.ContainsKey("sha256:layer1b"), "Expected layer1b group present");
|
||||
|
||||
var addedChange = layerGroups["sha256:layer3"].Changes.Single(change => change.Kind == ComponentChangeKind.Added);
|
||||
Assert.Equal("pkg:npm/c", addedChange.ComponentKey);
|
||||
Assert.NotNull(addedChange.NewComponent);
|
||||
|
||||
var versionChange = layerGroups["sha256:layer2"].Changes.Single(change => change.Kind == ComponentChangeKind.VersionChanged);
|
||||
Assert.Equal("pkg:npm/a", versionChange.ComponentKey);
|
||||
Assert.Equal("sha256:layer1b", versionChange.RemovingLayer);
|
||||
Assert.Equal("sha256:layer2", versionChange.IntroducingLayer);
|
||||
Assert.Equal("1.1.0", versionChange.NewComponent!.Identity.Version);
|
||||
|
||||
var metadataChange = layerGroups["sha256:layer3"].Changes.Single(change => change.Kind == ComponentChangeKind.MetadataChanged);
|
||||
Assert.True(metadataChange.NewComponent!.Usage.UsedByEntrypoint);
|
||||
Assert.False(metadataChange.OldComponent!.Usage.UsedByEntrypoint);
|
||||
Assert.Equal("sha256:layer3", metadataChange.IntroducingLayer);
|
||||
Assert.Equal("sha256:layer1", metadataChange.RemovingLayer);
|
||||
|
||||
var removedChange = layerGroups["sha256:layer1b"].Changes.Single(change => change.Kind == ComponentChangeKind.Removed);
|
||||
Assert.Equal("pkg:npm/d", removedChange.ComponentKey);
|
||||
Assert.Equal("sha256:layer1b", removedChange.RemovingLayer);
|
||||
Assert.Null(removedChange.IntroducingLayer);
|
||||
|
||||
var json = DiffJsonSerializer.Serialize(document);
|
||||
using var parsed = JsonDocument.Parse(json);
|
||||
var root = parsed.RootElement;
|
||||
using StellaOps.Scanner.Core.Contracts;
|
||||
using StellaOps.Scanner.Diff;
|
||||
using StellaOps.Scanner.Core.Utility;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Diff.Tests;
|
||||
|
||||
public sealed class ComponentDifferTests
|
||||
{
|
||||
[Fact]
|
||||
public void Compute_CapturesAddedRemovedAndChangedComponents()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:layer1", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:layer1",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/start.sh" }),
|
||||
evidence: new[] { ComponentEvidence.FromPath("/app/package-lock.json") }),
|
||||
CreateComponent("pkg:npm/b", version: "2.0.0", layer: "sha256:layer1", scope: "runtime"),
|
||||
}),
|
||||
LayerComponentFragment.Create("sha256:layer1b", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:layer1b",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/start.sh" })),
|
||||
CreateComponent("pkg:npm/d", version: "0.9.0", layer: "sha256:layer1b"),
|
||||
})
|
||||
};
|
||||
|
||||
var newFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:layer2", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.1.0",
|
||||
layer: "sha256:layer2",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/start.sh" }),
|
||||
evidence: new[] { ComponentEvidence.FromPath("/app/package-lock.json") }),
|
||||
}),
|
||||
LayerComponentFragment.Create("sha256:layer3", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/b",
|
||||
version: "2.0.0",
|
||||
layer: "sha256:layer3",
|
||||
usage: ComponentUsage.Create(true, new[] { "/app/init.sh" }),
|
||||
scope: "runtime"),
|
||||
CreateComponent("pkg:npm/c", version: "3.0.0", layer: "sha256:layer3"),
|
||||
})
|
||||
};
|
||||
|
||||
var oldGraph = ComponentGraphBuilder.Build(oldFragments);
|
||||
var newGraph = ComponentGraphBuilder.Build(newFragments);
|
||||
|
||||
var request = new ComponentDiffRequest
|
||||
{
|
||||
OldGraph = oldGraph,
|
||||
NewGraph = newGraph,
|
||||
GeneratedAt = new DateTimeOffset(2025, 10, 19, 10, 0, 0, TimeSpan.Zero),
|
||||
View = SbomView.Inventory,
|
||||
OldImageDigest = "sha256:old",
|
||||
NewImageDigest = "sha256:new",
|
||||
};
|
||||
|
||||
var differ = new ComponentDiffer();
|
||||
var document = differ.Compute(request);
|
||||
|
||||
Assert.Equal(SbomView.Inventory, document.View);
|
||||
Assert.Equal("sha256:old", document.OldImageDigest);
|
||||
Assert.Equal("sha256:new", document.NewImageDigest);
|
||||
Assert.Equal(1, document.Summary.Added);
|
||||
Assert.Equal(1, document.Summary.Removed);
|
||||
Assert.Equal(1, document.Summary.VersionChanged);
|
||||
Assert.Equal(1, document.Summary.MetadataChanged);
|
||||
|
||||
Assert.Equal(new[] { "sha256:layer2", "sha256:layer3", "sha256:layer1b" }, document.Layers.Select(layer => layer.LayerDigest));
|
||||
|
||||
var layerGroups = document.Layers.ToDictionary(layer => layer.LayerDigest);
|
||||
Assert.True(layerGroups.ContainsKey("sha256:layer2"), "Expected layer2 group present");
|
||||
Assert.True(layerGroups.ContainsKey("sha256:layer3"), "Expected layer3 group present");
|
||||
Assert.True(layerGroups.ContainsKey("sha256:layer1b"), "Expected layer1b group present");
|
||||
|
||||
var addedChange = layerGroups["sha256:layer3"].Changes.Single(change => change.Kind == ComponentChangeKind.Added);
|
||||
Assert.Equal("pkg:npm/c", addedChange.ComponentKey);
|
||||
Assert.NotNull(addedChange.NewComponent);
|
||||
|
||||
var versionChange = layerGroups["sha256:layer2"].Changes.Single(change => change.Kind == ComponentChangeKind.VersionChanged);
|
||||
Assert.Equal("pkg:npm/a", versionChange.ComponentKey);
|
||||
Assert.Equal("sha256:layer1b", versionChange.RemovingLayer);
|
||||
Assert.Equal("sha256:layer2", versionChange.IntroducingLayer);
|
||||
Assert.Equal("1.1.0", versionChange.NewComponent!.Identity.Version);
|
||||
|
||||
var metadataChange = layerGroups["sha256:layer3"].Changes.Single(change => change.Kind == ComponentChangeKind.MetadataChanged);
|
||||
Assert.True(metadataChange.NewComponent!.Usage.UsedByEntrypoint);
|
||||
Assert.False(metadataChange.OldComponent!.Usage.UsedByEntrypoint);
|
||||
Assert.Equal("sha256:layer3", metadataChange.IntroducingLayer);
|
||||
Assert.Equal("sha256:layer1", metadataChange.RemovingLayer);
|
||||
|
||||
var removedChange = layerGroups["sha256:layer1b"].Changes.Single(change => change.Kind == ComponentChangeKind.Removed);
|
||||
Assert.Equal("pkg:npm/d", removedChange.ComponentKey);
|
||||
Assert.Equal("sha256:layer1b", removedChange.RemovingLayer);
|
||||
Assert.Null(removedChange.IntroducingLayer);
|
||||
|
||||
var json = DiffJsonSerializer.Serialize(document);
|
||||
using var parsed = JsonDocument.Parse(json);
|
||||
var root = parsed.RootElement;
|
||||
Assert.Equal("inventory", root.GetProperty("view").GetString());
|
||||
var generatedAt = DateTimeOffset.Parse(root.GetProperty("generatedAt").GetString()!, CultureInfo.InvariantCulture);
|
||||
Assert.Equal(request.GeneratedAt, generatedAt);
|
||||
Assert.Equal("sha256:old", root.GetProperty("oldImageDigest").GetString());
|
||||
Assert.Equal("sha256:new", root.GetProperty("newImageDigest").GetString());
|
||||
|
||||
var summaryJson = root.GetProperty("summary");
|
||||
Assert.Equal(1, summaryJson.GetProperty("added").GetInt32());
|
||||
Assert.Equal(1, summaryJson.GetProperty("removed").GetInt32());
|
||||
Assert.Equal(1, summaryJson.GetProperty("versionChanged").GetInt32());
|
||||
Assert.Equal(1, summaryJson.GetProperty("metadataChanged").GetInt32());
|
||||
|
||||
var layersJson = root.GetProperty("layers");
|
||||
Assert.Equal(3, layersJson.GetArrayLength());
|
||||
|
||||
var layer2Json = layersJson[0];
|
||||
Assert.Equal("sha256:layer2", layer2Json.GetProperty("layerDigest").GetString());
|
||||
var layer2Changes = layer2Json.GetProperty("changes");
|
||||
Assert.Equal(1, layer2Changes.GetArrayLength());
|
||||
var versionChangeJson = layer2Changes.EnumerateArray().Single();
|
||||
Assert.Equal("versionChanged", versionChangeJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/a", versionChangeJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer2", versionChangeJson.GetProperty("introducingLayer").GetString());
|
||||
Assert.Equal("sha256:layer1b", versionChangeJson.GetProperty("removingLayer").GetString());
|
||||
Assert.Equal("1.1.0", versionChangeJson.GetProperty("newComponent").GetProperty("identity").GetProperty("version").GetString());
|
||||
|
||||
var layer3Json = layersJson[1];
|
||||
Assert.Equal("sha256:layer3", layer3Json.GetProperty("layerDigest").GetString());
|
||||
var layer3Changes = layer3Json.GetProperty("changes");
|
||||
Assert.Equal(2, layer3Changes.GetArrayLength());
|
||||
var layer3ChangeArray = layer3Changes.EnumerateArray().ToArray();
|
||||
var metadataChangeJson = layer3ChangeArray[0];
|
||||
Assert.Equal("metadataChanged", metadataChangeJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/b", metadataChangeJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer3", metadataChangeJson.GetProperty("introducingLayer").GetString());
|
||||
Assert.Equal("sha256:layer1", metadataChangeJson.GetProperty("removingLayer").GetString());
|
||||
Assert.True(metadataChangeJson.GetProperty("newComponent").GetProperty("usage").GetProperty("usedByEntrypoint").GetBoolean());
|
||||
Assert.False(metadataChangeJson.GetProperty("oldComponent").GetProperty("usage").GetProperty("usedByEntrypoint").GetBoolean());
|
||||
|
||||
var addedJson = layer3ChangeArray[1];
|
||||
Assert.Equal("added", addedJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/c", addedJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer3", addedJson.GetProperty("introducingLayer").GetString());
|
||||
Assert.False(addedJson.TryGetProperty("removingLayer", out _));
|
||||
|
||||
var removedLayerJson = layersJson[2];
|
||||
Assert.Equal("sha256:layer1b", removedLayerJson.GetProperty("layerDigest").GetString());
|
||||
var removedChanges = removedLayerJson.GetProperty("changes");
|
||||
Assert.Equal(1, removedChanges.GetArrayLength());
|
||||
var removedJson = removedChanges.EnumerateArray().Single();
|
||||
Assert.Equal("removed", removedJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/d", removedJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer1b", removedJson.GetProperty("removingLayer").GetString());
|
||||
Assert.False(removedJson.TryGetProperty("introducingLayer", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_UsageView_FiltersComponents()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:base", new[]
|
||||
{
|
||||
CreateComponent("pkg:npm/a", "1", "sha256:base", usage: ComponentUsage.Create(false)),
|
||||
})
|
||||
};
|
||||
|
||||
var newFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:new", new[]
|
||||
{
|
||||
CreateComponent("pkg:npm/a", "1", "sha256:new", usage: ComponentUsage.Create(false)),
|
||||
CreateComponent("pkg:npm/b", "1", "sha256:new", usage: ComponentUsage.Create(true, new[] { "/entry" })),
|
||||
})
|
||||
};
|
||||
|
||||
var request = new ComponentDiffRequest
|
||||
{
|
||||
OldGraph = ComponentGraphBuilder.Build(oldFragments),
|
||||
NewGraph = ComponentGraphBuilder.Build(newFragments),
|
||||
View = SbomView.Usage,
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
var differ = new ComponentDiffer();
|
||||
var document = differ.Compute(request);
|
||||
|
||||
Assert.Single(document.Layers);
|
||||
var layer = document.Layers[0];
|
||||
Assert.Single(layer.Changes);
|
||||
Assert.Equal(ComponentChangeKind.Added, layer.Changes[0].Kind);
|
||||
Assert.Equal("pkg:npm/b", layer.Changes[0].ComponentKey);
|
||||
|
||||
var json = DiffJsonSerializer.Serialize(document);
|
||||
using var parsed = JsonDocument.Parse(json);
|
||||
Assert.Equal("usage", parsed.RootElement.GetProperty("view").GetString());
|
||||
Assert.Equal(1, parsed.RootElement.GetProperty("summary").GetProperty("added").GetInt32());
|
||||
Assert.False(parsed.RootElement.TryGetProperty("oldImageDigest", out _));
|
||||
Assert.False(parsed.RootElement.TryGetProperty("newImageDigest", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
Assert.Equal("sha256:old", root.GetProperty("oldImageDigest").GetString());
|
||||
Assert.Equal("sha256:new", root.GetProperty("newImageDigest").GetString());
|
||||
|
||||
var summaryJson = root.GetProperty("summary");
|
||||
Assert.Equal(1, summaryJson.GetProperty("added").GetInt32());
|
||||
Assert.Equal(1, summaryJson.GetProperty("removed").GetInt32());
|
||||
Assert.Equal(1, summaryJson.GetProperty("versionChanged").GetInt32());
|
||||
Assert.Equal(1, summaryJson.GetProperty("metadataChanged").GetInt32());
|
||||
|
||||
var layersJson = root.GetProperty("layers");
|
||||
Assert.Equal(3, layersJson.GetArrayLength());
|
||||
|
||||
var layer2Json = layersJson[0];
|
||||
Assert.Equal("sha256:layer2", layer2Json.GetProperty("layerDigest").GetString());
|
||||
var layer2Changes = layer2Json.GetProperty("changes");
|
||||
Assert.Equal(1, layer2Changes.GetArrayLength());
|
||||
var versionChangeJson = layer2Changes.EnumerateArray().Single();
|
||||
Assert.Equal("versionChanged", versionChangeJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/a", versionChangeJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer2", versionChangeJson.GetProperty("introducingLayer").GetString());
|
||||
Assert.Equal("sha256:layer1b", versionChangeJson.GetProperty("removingLayer").GetString());
|
||||
Assert.Equal("1.1.0", versionChangeJson.GetProperty("newComponent").GetProperty("identity").GetProperty("version").GetString());
|
||||
|
||||
var layer3Json = layersJson[1];
|
||||
Assert.Equal("sha256:layer3", layer3Json.GetProperty("layerDigest").GetString());
|
||||
var layer3Changes = layer3Json.GetProperty("changes");
|
||||
Assert.Equal(2, layer3Changes.GetArrayLength());
|
||||
var layer3ChangeArray = layer3Changes.EnumerateArray().ToArray();
|
||||
var metadataChangeJson = layer3ChangeArray[0];
|
||||
Assert.Equal("metadataChanged", metadataChangeJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/b", metadataChangeJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer3", metadataChangeJson.GetProperty("introducingLayer").GetString());
|
||||
Assert.Equal("sha256:layer1", metadataChangeJson.GetProperty("removingLayer").GetString());
|
||||
Assert.True(metadataChangeJson.GetProperty("newComponent").GetProperty("usage").GetProperty("usedByEntrypoint").GetBoolean());
|
||||
Assert.False(metadataChangeJson.GetProperty("oldComponent").GetProperty("usage").GetProperty("usedByEntrypoint").GetBoolean());
|
||||
|
||||
var addedJson = layer3ChangeArray[1];
|
||||
Assert.Equal("added", addedJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/c", addedJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer3", addedJson.GetProperty("introducingLayer").GetString());
|
||||
Assert.False(addedJson.TryGetProperty("removingLayer", out _));
|
||||
|
||||
var removedLayerJson = layersJson[2];
|
||||
Assert.Equal("sha256:layer1b", removedLayerJson.GetProperty("layerDigest").GetString());
|
||||
var removedChanges = removedLayerJson.GetProperty("changes");
|
||||
Assert.Equal(1, removedChanges.GetArrayLength());
|
||||
var removedJson = removedChanges.EnumerateArray().Single();
|
||||
Assert.Equal("removed", removedJson.GetProperty("kind").GetString());
|
||||
Assert.Equal("pkg:npm/d", removedJson.GetProperty("componentKey").GetString());
|
||||
Assert.Equal("sha256:layer1b", removedJson.GetProperty("removingLayer").GetString());
|
||||
Assert.False(removedJson.TryGetProperty("introducingLayer", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_UsageView_FiltersComponents()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:base", new[]
|
||||
{
|
||||
CreateComponent("pkg:npm/a", "1", "sha256:base", usage: ComponentUsage.Create(false)),
|
||||
})
|
||||
};
|
||||
|
||||
var newFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:new", new[]
|
||||
{
|
||||
CreateComponent("pkg:npm/a", "1", "sha256:new", usage: ComponentUsage.Create(false)),
|
||||
CreateComponent("pkg:npm/b", "1", "sha256:new", usage: ComponentUsage.Create(true, new[] { "/entry" })),
|
||||
})
|
||||
};
|
||||
|
||||
var request = new ComponentDiffRequest
|
||||
{
|
||||
OldGraph = ComponentGraphBuilder.Build(oldFragments),
|
||||
NewGraph = ComponentGraphBuilder.Build(newFragments),
|
||||
View = SbomView.Usage,
|
||||
GeneratedAt = DateTimeOffset.UtcNow,
|
||||
};
|
||||
|
||||
var differ = new ComponentDiffer();
|
||||
var document = differ.Compute(request);
|
||||
|
||||
Assert.Single(document.Layers);
|
||||
var layer = document.Layers[0];
|
||||
Assert.Single(layer.Changes);
|
||||
Assert.Equal(ComponentChangeKind.Added, layer.Changes[0].Kind);
|
||||
Assert.Equal("pkg:npm/b", layer.Changes[0].ComponentKey);
|
||||
|
||||
var json = DiffJsonSerializer.Serialize(document);
|
||||
using var parsed = JsonDocument.Parse(json);
|
||||
Assert.Equal("usage", parsed.RootElement.GetProperty("view").GetString());
|
||||
Assert.Equal(1, parsed.RootElement.GetProperty("summary").GetProperty("added").GetInt32());
|
||||
Assert.False(parsed.RootElement.TryGetProperty("oldImageDigest", out _));
|
||||
Assert.False(parsed.RootElement.TryGetProperty("newImageDigest", out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Compute_MetadataChange_WhenEvidenceDiffers()
|
||||
{
|
||||
var oldFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:underlay", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:underlay",
|
||||
usage: ComponentUsage.Create(false),
|
||||
evidence: new[] { ComponentEvidence.FromPath("/workspace/package-lock.json") }),
|
||||
}),
|
||||
};
|
||||
|
||||
var newFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:overlay", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:overlay",
|
||||
usage: ComponentUsage.Create(false),
|
||||
evidence: new[]
|
||||
{
|
||||
ComponentEvidence.FromPath("/workspace/package-lock.json"),
|
||||
ComponentEvidence.FromPath("/workspace/yarn.lock"),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
var request = new ComponentDiffRequest
|
||||
{
|
||||
OldGraph = ComponentGraphBuilder.Build(oldFragments),
|
||||
NewGraph = ComponentGraphBuilder.Build(newFragments),
|
||||
GeneratedAt = new DateTimeOffset(2025, 10, 19, 12, 0, 0, TimeSpan.Zero),
|
||||
};
|
||||
|
||||
var differ = new ComponentDiffer();
|
||||
var document = differ.Compute(request);
|
||||
|
||||
Assert.Equal(0, document.Summary.Added);
|
||||
Assert.Equal(0, document.Summary.Removed);
|
||||
Assert.Equal(0, document.Summary.VersionChanged);
|
||||
Assert.Equal(1, document.Summary.MetadataChanged);
|
||||
|
||||
var layer = Assert.Single(document.Layers);
|
||||
Assert.Equal("sha256:overlay", layer.LayerDigest);
|
||||
|
||||
var change = Assert.Single(layer.Changes);
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:underlay",
|
||||
usage: ComponentUsage.Create(false),
|
||||
evidence: new[] { ComponentEvidence.FromPath("/workspace/package-lock.json") }),
|
||||
}),
|
||||
};
|
||||
|
||||
var newFragments = new[]
|
||||
{
|
||||
LayerComponentFragment.Create("sha256:overlay", new[]
|
||||
{
|
||||
CreateComponent(
|
||||
"pkg:npm/a",
|
||||
version: "1.0.0",
|
||||
layer: "sha256:overlay",
|
||||
usage: ComponentUsage.Create(false),
|
||||
evidence: new[]
|
||||
{
|
||||
ComponentEvidence.FromPath("/workspace/package-lock.json"),
|
||||
ComponentEvidence.FromPath("/workspace/yarn.lock"),
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
var request = new ComponentDiffRequest
|
||||
{
|
||||
OldGraph = ComponentGraphBuilder.Build(oldFragments),
|
||||
NewGraph = ComponentGraphBuilder.Build(newFragments),
|
||||
GeneratedAt = new DateTimeOffset(2025, 10, 19, 12, 0, 0, TimeSpan.Zero),
|
||||
};
|
||||
|
||||
var differ = new ComponentDiffer();
|
||||
var document = differ.Compute(request);
|
||||
|
||||
Assert.Equal(0, document.Summary.Added);
|
||||
Assert.Equal(0, document.Summary.Removed);
|
||||
Assert.Equal(0, document.Summary.VersionChanged);
|
||||
Assert.Equal(1, document.Summary.MetadataChanged);
|
||||
|
||||
var layer = Assert.Single(document.Layers);
|
||||
Assert.Equal("sha256:overlay", layer.LayerDigest);
|
||||
|
||||
var change = Assert.Single(layer.Changes);
|
||||
Assert.Equal(ComponentChangeKind.MetadataChanged, change.Kind);
|
||||
Assert.Equal("sha256:overlay", change.IntroducingLayer);
|
||||
Assert.Equal("sha256:underlay", change.RemovingLayer);
|
||||
@@ -339,7 +339,7 @@ public sealed class ComponentDifferTests
|
||||
Assert.Equal("abcdef1234567890abcdef1234567890abcdef12", changeJson.GetProperty("oldComponent").GetProperty("metadata").GetProperty("buildId").GetString());
|
||||
Assert.Equal("6e0d8f6aa1b2c3d4e5f60718293a4b5c6d7e8f90", changeJson.GetProperty("newComponent").GetProperty("metadata").GetProperty("buildId").GetString());
|
||||
}
|
||||
|
||||
|
||||
private static ComponentRecord CreateComponent(
|
||||
string key,
|
||||
string version,
|
||||
|
||||
Reference in New Issue
Block a user