Fix build and code structure improvements. New but essential UI functionality. CI improvements. Documentation improvements. AI module improvements.
This commit is contained in:
@@ -0,0 +1,214 @@
|
||||
// Licensed to StellaOps under the AGPL-3.0-or-later license.
|
||||
|
||||
using StellaOps.ReachGraph.Hashing;
|
||||
using StellaOps.ReachGraph.Schema;
|
||||
using StellaOps.ReachGraph.Serialization;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.ReachGraph.Tests;
|
||||
|
||||
public class DigestComputerTests
|
||||
{
|
||||
private readonly CanonicalReachGraphSerializer _serializer = new();
|
||||
private readonly ReachGraphDigestComputer _digestComputer;
|
||||
|
||||
public DigestComputerTests()
|
||||
{
|
||||
_digestComputer = new ReachGraphDigestComputer(_serializer);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeDigest_WithSameInput_ProducesSameDigest()
|
||||
{
|
||||
// Arrange
|
||||
var graph = CreateSampleGraph();
|
||||
|
||||
// Act
|
||||
var digest1 = _digestComputer.ComputeDigest(graph);
|
||||
var digest2 = _digestComputer.ComputeDigest(graph);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(digest1, digest2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeDigest_ReturnsBlake3Format()
|
||||
{
|
||||
// Arrange
|
||||
var graph = CreateSampleGraph();
|
||||
|
||||
// Act
|
||||
var digest = _digestComputer.ComputeDigest(graph);
|
||||
|
||||
// Assert
|
||||
Assert.StartsWith("blake3:", digest);
|
||||
Assert.Equal(71, digest.Length); // "blake3:" (7) + 64 hex chars
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeDigest_ExcludesSignatures()
|
||||
{
|
||||
// Arrange
|
||||
var unsigned = CreateSampleGraph();
|
||||
var signed = unsigned with
|
||||
{
|
||||
Signatures = [new ReachGraphSignature("key-1", "sig-base64")]
|
||||
};
|
||||
|
||||
// Act
|
||||
var digestUnsigned = _digestComputer.ComputeDigest(unsigned);
|
||||
var digestSigned = _digestComputer.ComputeDigest(signed);
|
||||
|
||||
// Assert - signatures should not affect digest
|
||||
Assert.Equal(digestUnsigned, digestSigned);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeDigest_DifferentInputs_ProduceDifferentDigests()
|
||||
{
|
||||
// Arrange
|
||||
var graph1 = CreateSampleGraph();
|
||||
var graph2 = graph1 with
|
||||
{
|
||||
Artifact = new ReachGraphArtifact("different-app", "sha256:different", ["linux/amd64"])
|
||||
};
|
||||
|
||||
// Act
|
||||
var digest1 = _digestComputer.ComputeDigest(graph1);
|
||||
var digest2 = _digestComputer.ComputeDigest(graph2);
|
||||
|
||||
// Assert
|
||||
Assert.NotEqual(digest1, digest2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyDigest_ValidDigest_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var graph = CreateSampleGraph();
|
||||
var digest = _digestComputer.ComputeDigest(graph);
|
||||
|
||||
// Act
|
||||
var result = _digestComputer.VerifyDigest(graph, digest);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VerifyDigest_InvalidDigest_ReturnsFalse()
|
||||
{
|
||||
// Arrange
|
||||
var graph = CreateSampleGraph();
|
||||
var wrongDigest = "blake3:0000000000000000000000000000000000000000000000000000000000000000";
|
||||
|
||||
// Act
|
||||
var result = _digestComputer.VerifyDigest(graph, wrongDigest);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsValidBlake3Digest_ValidFormat_ReturnsTrue()
|
||||
{
|
||||
// Arrange
|
||||
var validDigest = "blake3:abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789";
|
||||
|
||||
// Act
|
||||
var result = ReachGraphDigestComputer.IsValidBlake3Digest(validDigest);
|
||||
|
||||
// Assert
|
||||
Assert.True(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("sha256:abcdef")] // Wrong algorithm
|
||||
[InlineData("blake3:short")] // Too short
|
||||
[InlineData("blake3:ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ")] // Invalid hex
|
||||
[InlineData("")] // Empty
|
||||
[InlineData("blake3")] // No colon
|
||||
public void IsValidBlake3Digest_InvalidFormat_ReturnsFalse(string digest)
|
||||
{
|
||||
// Act
|
||||
var result = ReachGraphDigestComputer.IsValidBlake3Digest(digest);
|
||||
|
||||
// Assert
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseDigest_ValidFormat_ReturnsComponents()
|
||||
{
|
||||
// Arrange
|
||||
var digest = "blake3:abc123def456";
|
||||
|
||||
// Act
|
||||
var result = ReachGraphDigestComputer.ParseDigest(digest);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal("blake3", result.Value.Algorithm);
|
||||
Assert.Equal("abc123def456", result.Value.Hash);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("")]
|
||||
[InlineData("nocolon")]
|
||||
[InlineData(":noleft")]
|
||||
[InlineData("noright:")]
|
||||
public void ParseDigest_InvalidFormat_ReturnsNull(string digest)
|
||||
{
|
||||
// Act
|
||||
var result = ReachGraphDigestComputer.ParseDigest(digest);
|
||||
|
||||
// Assert
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeDigest_IsDeterministic_AcrossNodeOrdering()
|
||||
{
|
||||
// Arrange - nodes in different order
|
||||
var graph1 = CreateSampleGraph() with
|
||||
{
|
||||
Nodes =
|
||||
[
|
||||
new ReachGraphNode { Id = "sha256:aaa", Kind = ReachGraphNodeKind.Function, Ref = "a()" },
|
||||
new ReachGraphNode { Id = "sha256:bbb", Kind = ReachGraphNodeKind.Function, Ref = "b()" }
|
||||
]
|
||||
};
|
||||
var graph2 = CreateSampleGraph() with
|
||||
{
|
||||
Nodes =
|
||||
[
|
||||
new ReachGraphNode { Id = "sha256:bbb", Kind = ReachGraphNodeKind.Function, Ref = "b()" },
|
||||
new ReachGraphNode { Id = "sha256:aaa", Kind = ReachGraphNodeKind.Function, Ref = "a()" }
|
||||
]
|
||||
};
|
||||
|
||||
// Act
|
||||
var digest1 = _digestComputer.ComputeDigest(graph1);
|
||||
var digest2 = _digestComputer.ComputeDigest(graph2);
|
||||
|
||||
// Assert - canonical serialization should produce same digest regardless of input order
|
||||
Assert.Equal(digest1, digest2);
|
||||
}
|
||||
|
||||
private static ReachGraphMinimal CreateSampleGraph() => new()
|
||||
{
|
||||
Artifact = new ReachGraphArtifact("test-app", "sha256:abc123", ["linux/amd64"]),
|
||||
Scope = new ReachGraphScope(["/app/main"], ["prod"]),
|
||||
Nodes =
|
||||
[
|
||||
new ReachGraphNode { Id = "sha256:001", Kind = ReachGraphNodeKind.Function, Ref = "main()" }
|
||||
],
|
||||
Edges = [],
|
||||
Provenance = new ReachGraphProvenance
|
||||
{
|
||||
Inputs = new ReachGraphInputs { Sbom = "sha256:sbom123" },
|
||||
ComputedAt = new DateTimeOffset(2025, 12, 27, 10, 0, 0, TimeSpan.Zero),
|
||||
Analyzer = new ReachGraphAnalyzer("test", "1.0.0", "sha256:toolchain")
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user