up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (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
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (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
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,484 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Tests;
|
||||
|
||||
public class EdgeBundleTests
|
||||
{
|
||||
private const string TestGraphHash = "blake3:abc123def456";
|
||||
|
||||
[Fact]
|
||||
public void EdgeBundle_Canonical_SortsEdgesDeterministically()
|
||||
{
|
||||
// Arrange - create bundle with unsorted edges
|
||||
var edges = new List<BundledEdge>
|
||||
{
|
||||
new("func_z", "func_a", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
|
||||
new("func_a", "func_c", "call", EdgeReason.RuntimeHit, false, 0.8, null, null, null),
|
||||
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.7, null, null, null),
|
||||
};
|
||||
|
||||
var bundle = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var canonical = bundle.Canonical();
|
||||
|
||||
// Assert - edges should be sorted by From, then To, then Kind
|
||||
Assert.Equal(3, canonical.Edges.Count);
|
||||
Assert.Equal("func_a", canonical.Edges[0].From);
|
||||
Assert.Equal("func_b", canonical.Edges[0].To);
|
||||
Assert.Equal("func_a", canonical.Edges[1].From);
|
||||
Assert.Equal("func_c", canonical.Edges[1].To);
|
||||
Assert.Equal("func_z", canonical.Edges[2].From);
|
||||
Assert.Equal("func_a", canonical.Edges[2].To);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EdgeBundle_ComputeContentHash_IsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var edges = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
|
||||
new("func_b", "func_c", "call", EdgeReason.ThirdPartyCall, false, 0.8, null, null, null),
|
||||
};
|
||||
|
||||
var bundle1 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
|
||||
var bundle2 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow.AddMinutes(5));
|
||||
|
||||
// Act
|
||||
var hash1 = bundle1.ComputeContentHash();
|
||||
var hash2 = bundle2.ComputeContentHash();
|
||||
|
||||
// Assert - same content should produce same hash regardless of timestamp
|
||||
Assert.Equal(hash1, hash2);
|
||||
Assert.StartsWith("sha256:", hash1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EdgeBundle_ComputeContentHash_DiffersWithDifferentEdges()
|
||||
{
|
||||
// Arrange
|
||||
var edges1 = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
|
||||
};
|
||||
var edges2 = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_c", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
|
||||
};
|
||||
|
||||
var bundle1 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges1, DateTimeOffset.UtcNow);
|
||||
var bundle2 = new EdgeBundle("bundle:test", TestGraphHash, EdgeBundleReason.RuntimeHits, edges2, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var hash1 = bundle1.ComputeContentHash();
|
||||
var hash2 = bundle2.ComputeContentHash();
|
||||
|
||||
// Assert - different edges should produce different hashes
|
||||
Assert.NotEqual(hash1, hash2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EdgeBundleBuilder_EnforcesMaxEdgeLimit()
|
||||
{
|
||||
// Arrange
|
||||
var builder = new EdgeBundleBuilder(TestGraphHash).WithReason(EdgeBundleReason.RuntimeHits);
|
||||
|
||||
// Act - add max edges
|
||||
for (var i = 0; i < EdgeBundleConstants.MaxEdgesPerBundle; i++)
|
||||
{
|
||||
builder.AddEdge(new BundledEdge($"func_{i}", $"func_{i + 1}", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null));
|
||||
}
|
||||
|
||||
// Assert - should throw when exceeding limit
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
builder.AddEdge(new BundledEdge("func_overflow", "func_target", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EdgeBundleBuilder_Build_CreatesDeterministicBundleId()
|
||||
{
|
||||
// Arrange
|
||||
var builder1 = new EdgeBundleBuilder(TestGraphHash).WithReason(EdgeBundleReason.InitArray);
|
||||
var builder2 = new EdgeBundleBuilder(TestGraphHash).WithReason(EdgeBundleReason.InitArray);
|
||||
|
||||
builder1.AddEdge(new BundledEdge("init_a", "func_b", "call", EdgeReason.InitArray, false, 1.0, null, null, null));
|
||||
builder2.AddEdge(new BundledEdge("init_a", "func_b", "call", EdgeReason.InitArray, false, 1.0, null, null, null));
|
||||
|
||||
// Act
|
||||
var bundle1 = builder1.Build();
|
||||
var bundle2 = builder2.Build();
|
||||
|
||||
// Assert - same inputs should produce same bundle ID
|
||||
Assert.Equal(bundle1.BundleId, bundle2.BundleId);
|
||||
Assert.StartsWith("bundle:", bundle1.BundleId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundledEdge_Trimmed_NormalizesValues()
|
||||
{
|
||||
// Arrange
|
||||
var edge = new BundledEdge(
|
||||
From: " func_a ",
|
||||
To: " func_b ",
|
||||
Kind: " call ",
|
||||
Reason: EdgeReason.RuntimeHit,
|
||||
Revoked: false,
|
||||
Confidence: 1.5, // Should be clamped to 1.0
|
||||
Purl: " pkg:npm/test@1.0.0 ",
|
||||
SymbolDigest: " sha256:abc ",
|
||||
Evidence: " cas://evidence/123 ");
|
||||
|
||||
// Act
|
||||
var trimmed = edge.Trimmed();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("func_a", trimmed.From);
|
||||
Assert.Equal("func_b", trimmed.To);
|
||||
Assert.Equal("call", trimmed.Kind);
|
||||
Assert.Equal(1.0, trimmed.Confidence); // Clamped
|
||||
Assert.Equal("pkg:npm/test@1.0.0", trimmed.Purl);
|
||||
Assert.Equal("sha256:abc", trimmed.SymbolDigest);
|
||||
Assert.Equal("cas://evidence/123", trimmed.Evidence);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BundledEdge_Trimmed_HandlesNullableFields()
|
||||
{
|
||||
// Arrange
|
||||
var edge = new BundledEdge("func_a", "func_b", "", EdgeReason.RuntimeHit, false, 0.5, null, " ", null);
|
||||
|
||||
// Act
|
||||
var trimmed = edge.Trimmed();
|
||||
|
||||
// Assert
|
||||
Assert.Equal("call", trimmed.Kind); // Default when empty
|
||||
Assert.Null(trimmed.Purl);
|
||||
Assert.Null(trimmed.SymbolDigest); // Whitespace trimmed to null
|
||||
Assert.Null(trimmed.Evidence);
|
||||
}
|
||||
}
|
||||
|
||||
public class EdgeBundleExtractorTests
|
||||
{
|
||||
private const string TestGraphHash = "blake3:abc123def456";
|
||||
|
||||
private static RichGraph CreateTestGraph(params RichGraphEdge[] edges)
|
||||
{
|
||||
var nodes = edges
|
||||
.SelectMany(e => new[] { e.From, e.To })
|
||||
.Distinct()
|
||||
.Select(id => new RichGraphNode(id, id, null, null, "native", "function", id, null, null, null, null, null, null))
|
||||
.ToList();
|
||||
|
||||
return new RichGraph(nodes, edges.ToList(), new List<RichGraphRoot>(), new RichGraphAnalyzer("test", "1.0", null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractContestedBundle_ReturnsLowConfidenceEdges()
|
||||
{
|
||||
// Arrange
|
||||
var edges = new[]
|
||||
{
|
||||
new RichGraphEdge("func_a", "func_b", "call", null, null, null, 0.9, null),
|
||||
new RichGraphEdge("func_b", "func_c", "call", null, null, null, 0.4, null), // Low confidence
|
||||
new RichGraphEdge("func_c", "func_d", "call", null, null, null, 0.3, null), // Low confidence
|
||||
};
|
||||
var graph = CreateTestGraph(edges);
|
||||
|
||||
// Act
|
||||
var bundle = EdgeBundleExtractor.ExtractContestedBundle(graph, TestGraphHash, confidenceThreshold: 0.5);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundle);
|
||||
Assert.Equal(EdgeBundleReason.Contested, bundle.BundleReason);
|
||||
Assert.Equal(2, bundle.Edges.Count);
|
||||
Assert.All(bundle.Edges, e => Assert.Equal(EdgeReason.LowConfidence, e.Reason));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractContestedBundle_ReturnsNullWhenNoLowConfidenceEdges()
|
||||
{
|
||||
// Arrange
|
||||
var edges = new[]
|
||||
{
|
||||
new RichGraphEdge("func_a", "func_b", "call", null, null, null, 0.9, null),
|
||||
new RichGraphEdge("func_b", "func_c", "call", null, null, null, 0.8, null),
|
||||
};
|
||||
var graph = CreateTestGraph(edges);
|
||||
|
||||
// Act
|
||||
var bundle = EdgeBundleExtractor.ExtractContestedBundle(graph, TestGraphHash, confidenceThreshold: 0.5);
|
||||
|
||||
// Assert
|
||||
Assert.Null(bundle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractThirdPartyBundle_ReturnsEdgesWithPurl()
|
||||
{
|
||||
// Arrange
|
||||
var edges = new[]
|
||||
{
|
||||
new RichGraphEdge("func_a", "func_b", "call", "pkg:npm/lodash@4.17.0", null, null, 0.9, null),
|
||||
new RichGraphEdge("func_b", "func_c", "call", "pkg:unknown", null, null, 0.8, null), // Excluded
|
||||
new RichGraphEdge("func_c", "func_d", "call", null, null, null, 0.7, null), // Excluded
|
||||
new RichGraphEdge("func_d", "func_e", "call", "pkg:npm/express@4.0.0", null, null, 0.9, null),
|
||||
};
|
||||
var graph = CreateTestGraph(edges);
|
||||
|
||||
// Act
|
||||
var bundle = EdgeBundleExtractor.ExtractThirdPartyBundle(graph, TestGraphHash);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundle);
|
||||
Assert.Equal(EdgeBundleReason.ThirdParty, bundle.BundleReason);
|
||||
Assert.Equal(2, bundle.Edges.Count);
|
||||
Assert.All(bundle.Edges, e => Assert.Equal(EdgeReason.ThirdPartyCall, e.Reason));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractRevokedBundle_ReturnsEdgesToRevokedTargets()
|
||||
{
|
||||
// Arrange
|
||||
var edges = new[]
|
||||
{
|
||||
new RichGraphEdge("func_a", "func_b", "call", null, null, null, 0.9, null),
|
||||
new RichGraphEdge("func_b", "func_c", "call", null, null, null, 0.8, null),
|
||||
new RichGraphEdge("func_c", "func_d", "call", null, null, null, 0.7, null),
|
||||
};
|
||||
var graph = CreateTestGraph(edges);
|
||||
var revokedTargets = new HashSet<string> { "func_c", "func_d" };
|
||||
|
||||
// Act
|
||||
var bundle = EdgeBundleExtractor.ExtractRevokedBundle(graph, TestGraphHash, revokedTargets);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundle);
|
||||
Assert.Equal(EdgeBundleReason.Revoked, bundle.BundleReason);
|
||||
Assert.Equal(2, bundle.Edges.Count);
|
||||
Assert.All(bundle.Edges, e =>
|
||||
{
|
||||
Assert.Equal(EdgeReason.Revoked, e.Reason);
|
||||
Assert.True(e.Revoked);
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractRuntimeHitsBundle_ReturnsProvidedEdges()
|
||||
{
|
||||
// Arrange
|
||||
var runtimeEdges = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 1.0, null, null, "evidence_1"),
|
||||
new("func_b", "func_c", "call", EdgeReason.RuntimeHit, false, 1.0, null, null, "evidence_2"),
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = EdgeBundleExtractor.ExtractRuntimeHitsBundle(runtimeEdges, TestGraphHash);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundle);
|
||||
Assert.Equal(EdgeBundleReason.RuntimeHits, bundle.BundleReason);
|
||||
Assert.Equal(2, bundle.Edges.Count);
|
||||
Assert.All(bundle.Edges, e => Assert.Equal(EdgeReason.RuntimeHit, e.Reason));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractRuntimeHitsBundle_ReturnsNullForEmptyList()
|
||||
{
|
||||
// Act
|
||||
var bundle = EdgeBundleExtractor.ExtractRuntimeHitsBundle(new List<BundledEdge>(), TestGraphHash);
|
||||
|
||||
// Assert
|
||||
Assert.Null(bundle);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtractInitArrayBundle_ReturnsEdgesFromInitRoots()
|
||||
{
|
||||
// Arrange
|
||||
var edges = new[]
|
||||
{
|
||||
new RichGraphEdge("init_func", "target_a", "call", null, null, null, 1.0, null),
|
||||
new RichGraphEdge("init_func", "target_b", "call", null, null, null, 1.0, null),
|
||||
new RichGraphEdge("main_func", "target_c", "call", null, null, null, 0.9, null), // Not from init
|
||||
};
|
||||
var nodes = edges
|
||||
.SelectMany(e => new[] { e.From, e.To })
|
||||
.Distinct()
|
||||
.Select(id => new RichGraphNode(id, id, null, null, "native", "function", id, null, null, null, null, null, null))
|
||||
.ToList();
|
||||
var roots = new List<RichGraphRoot>
|
||||
{
|
||||
new("init_func", "init", ".init_array")
|
||||
};
|
||||
var graph = new RichGraph(nodes, edges.ToList(), roots, new RichGraphAnalyzer("test", "1.0", null));
|
||||
|
||||
// Act
|
||||
var bundle = EdgeBundleExtractor.ExtractInitArrayBundle(graph, TestGraphHash);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundle);
|
||||
Assert.Equal(EdgeBundleReason.InitArray, bundle.BundleReason);
|
||||
Assert.Equal(2, bundle.Edges.Count);
|
||||
}
|
||||
}
|
||||
|
||||
public class EdgeBundlePublisherTests
|
||||
{
|
||||
private const string TestGraphHash = "blake3:abc123def456";
|
||||
|
||||
[Fact]
|
||||
public async Task PublishAsync_StoresBundleAndDsseInCas()
|
||||
{
|
||||
// Arrange
|
||||
var cas = new FakeFileContentAddressableStore();
|
||||
var publisher = new EdgeBundlePublisher();
|
||||
|
||||
var edges = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, "pkg:npm/test@1.0.0", "sha256:abc", null),
|
||||
};
|
||||
var bundle = new EdgeBundle("bundle:test123", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var result = await publisher.PublishAsync(bundle, cas);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("bundle:test123", result.BundleId);
|
||||
Assert.Equal(TestGraphHash, result.GraphHash);
|
||||
Assert.Equal(EdgeBundleReason.RuntimeHits, result.BundleReason);
|
||||
Assert.Equal(1, result.EdgeCount);
|
||||
Assert.StartsWith("sha256:", result.ContentHash);
|
||||
Assert.StartsWith("sha256:", result.DsseDigest);
|
||||
|
||||
// Verify CAS paths
|
||||
Assert.Contains("/edges/", result.CasUri);
|
||||
Assert.Contains("/edges/", result.DsseCasUri);
|
||||
Assert.EndsWith(".dsse", result.DsseCasUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PublishAsync_DsseContainsValidPayload()
|
||||
{
|
||||
// Arrange
|
||||
var cas = new FakeFileContentAddressableStore();
|
||||
var publisher = new EdgeBundlePublisher();
|
||||
|
||||
var edges = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
|
||||
new("func_b", "func_c", "call", EdgeReason.ThirdPartyCall, true, 0.8, null, null, null),
|
||||
};
|
||||
var bundle = new EdgeBundle("bundle:test456", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var result = await publisher.PublishAsync(bundle, cas);
|
||||
|
||||
// Assert - verify DSSE was stored
|
||||
var dsseKey = result.DsseRelativePath.Replace(".zip", "");
|
||||
var dsseBytes = cas.GetBytes(dsseKey);
|
||||
Assert.NotNull(dsseBytes);
|
||||
|
||||
// Parse DSSE envelope
|
||||
var dsseJson = System.Text.Encoding.UTF8.GetString(dsseBytes);
|
||||
var envelope = JsonDocument.Parse(dsseJson);
|
||||
|
||||
Assert.Equal("application/vnd.stellaops.edgebundle.predicate+json", envelope.RootElement.GetProperty("payloadType").GetString());
|
||||
Assert.True(envelope.RootElement.TryGetProperty("payload", out _));
|
||||
Assert.True(envelope.RootElement.TryGetProperty("signatures", out var signatures));
|
||||
Assert.Single(signatures.EnumerateArray());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PublishAsync_BundleJsonContainsAllFields()
|
||||
{
|
||||
// Arrange
|
||||
var cas = new FakeFileContentAddressableStore();
|
||||
var publisher = new EdgeBundlePublisher();
|
||||
|
||||
var edges = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.Revoked, true, 0.5, "pkg:npm/test@1.0.0", "sha256:digest", "cas://evidence/123"),
|
||||
};
|
||||
var bundle = new EdgeBundle("bundle:revoked", TestGraphHash, EdgeBundleReason.Revoked, edges, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var result = await publisher.PublishAsync(bundle, cas);
|
||||
|
||||
// Assert - verify bundle JSON was stored
|
||||
var bundleKey = result.RelativePath.Replace(".zip", "");
|
||||
var bundleBytes = cas.GetBytes(bundleKey);
|
||||
Assert.NotNull(bundleBytes);
|
||||
|
||||
// Parse bundle JSON
|
||||
var bundleJsonStr = System.Text.Encoding.UTF8.GetString(bundleBytes);
|
||||
var bundleJson = JsonDocument.Parse(bundleJsonStr);
|
||||
|
||||
Assert.Equal("edge-bundle-v1", bundleJson.RootElement.GetProperty("schema").GetString());
|
||||
Assert.Equal("Revoked", bundleJson.RootElement.GetProperty("bundleReason").GetString());
|
||||
|
||||
var edgesArray = bundleJson.RootElement.GetProperty("edges");
|
||||
Assert.Single(edgesArray.EnumerateArray());
|
||||
|
||||
var edge = edgesArray[0];
|
||||
Assert.Equal("func_a", edge.GetProperty("from").GetString());
|
||||
Assert.Equal("func_b", edge.GetProperty("to").GetString());
|
||||
Assert.Equal("Revoked", edge.GetProperty("reason").GetString());
|
||||
Assert.True(edge.GetProperty("revoked").GetBoolean());
|
||||
Assert.Equal("pkg:npm/test@1.0.0", edge.GetProperty("purl").GetString());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PublishAsync_CasPathFollowsContract()
|
||||
{
|
||||
// Arrange
|
||||
var cas = new FakeFileContentAddressableStore();
|
||||
var publisher = new EdgeBundlePublisher();
|
||||
|
||||
var edges = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.InitArray, false, 1.0, null, null, null),
|
||||
};
|
||||
var bundle = new EdgeBundle("bundle:init123", TestGraphHash, EdgeBundleReason.InitArray, edges, DateTimeOffset.UtcNow);
|
||||
|
||||
// Act
|
||||
var result = await publisher.PublishAsync(bundle, cas);
|
||||
|
||||
// Assert - CAS path follows contract: cas://reachability/edges/{graph_hash}/{bundle_id}
|
||||
var expectedGraphHashDigest = "abc123def456"; // Graph hash without prefix
|
||||
Assert.StartsWith($"cas://reachability/edges/{expectedGraphHashDigest}/", result.CasUri);
|
||||
Assert.StartsWith($"cas://reachability/edges/{expectedGraphHashDigest}/", result.DsseCasUri);
|
||||
Assert.EndsWith(".dsse", result.DsseCasUri);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PublishAsync_ProducesDeterministicResults()
|
||||
{
|
||||
// Arrange
|
||||
var cas1 = new FakeFileContentAddressableStore();
|
||||
var cas2 = new FakeFileContentAddressableStore();
|
||||
var publisher = new EdgeBundlePublisher();
|
||||
|
||||
var edges = new List<BundledEdge>
|
||||
{
|
||||
new("func_a", "func_b", "call", EdgeReason.RuntimeHit, false, 0.9, null, null, null),
|
||||
};
|
||||
var bundle1 = new EdgeBundle("bundle:det", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow);
|
||||
var bundle2 = new EdgeBundle("bundle:det", TestGraphHash, EdgeBundleReason.RuntimeHits, edges, DateTimeOffset.UtcNow.AddHours(1));
|
||||
|
||||
// Act
|
||||
var result1 = await publisher.PublishAsync(bundle1, cas1);
|
||||
var result2 = await publisher.PublishAsync(bundle2, cas2);
|
||||
|
||||
// Assert - content hash should be same for same content
|
||||
Assert.Equal(result1.ContentHash, result2.ContentHash);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user