Files
git.stella-ops.org/src/BinaryIndex/__Tests/StellaOps.BinaryIndex.Diff.Tests/Unit/PatchDiffModelTests.cs

245 lines
7.1 KiB
C#

// Licensed under BUSL-1.1. Copyright (C) 2026 StellaOps Contributors.
using System.Collections.Immutable;
using FluentAssertions;
using Xunit;
namespace StellaOps.BinaryIndex.Diff.Tests.Unit;
[Trait("Category", "Unit")]
public sealed class PatchDiffModelTests
{
[Fact]
public void PatchDiffResult_NoPatchDetected_CreatesCorrectResult()
{
// Arrange
var goldenSetId = "CVE-2024-1234";
var goldenSetDigest = "sha256:abcd1234";
var binaryDigest = "sha256:same1234";
var comparedAt = DateTimeOffset.UtcNow;
var duration = TimeSpan.FromMilliseconds(100);
var options = DiffOptions.Default;
// Act
var result = PatchDiffResult.NoPatchDetected(
goldenSetId, goldenSetDigest, binaryDigest,
comparedAt, duration, options);
// Assert
result.GoldenSetId.Should().Be(goldenSetId);
result.Verdict.Should().Be(PatchVerdict.NoPatchDetected);
result.Confidence.Should().Be(1.0m);
result.PreBinaryDigest.Should().Be(binaryDigest);
result.PostBinaryDigest.Should().Be(binaryDigest);
result.Evidence.Should().HaveCount(1);
result.Evidence[0].Type.Should().Be(DiffEvidenceType.IdenticalBinaries);
}
[Theory]
[InlineData(PatchVerdict.Fixed)]
[InlineData(PatchVerdict.PartialFix)]
[InlineData(PatchVerdict.StillVulnerable)]
[InlineData(PatchVerdict.Inconclusive)]
[InlineData(PatchVerdict.NoPatchDetected)]
public void PatchVerdict_AllValuesAreDefined(PatchVerdict verdict)
{
// Assert
Enum.IsDefined(verdict).Should().BeTrue();
}
[Fact]
public void FunctionDiffResult_FunctionRemoved_CreatesCorrectResult()
{
// Act
var result = FunctionDiffResult.FunctionRemoved("vulnerable_func");
// Assert
result.FunctionName.Should().Be("vulnerable_func");
result.PreStatus.Should().Be(FunctionStatus.Present);
result.PostStatus.Should().Be(FunctionStatus.Absent);
result.Verdict.Should().Be(FunctionPatchVerdict.FunctionRemoved);
}
[Fact]
public void FunctionDiffResult_NotFound_CreatesCorrectResult()
{
// Act
var result = FunctionDiffResult.NotFound("missing_func");
// Assert
result.FunctionName.Should().Be("missing_func");
result.PreStatus.Should().Be(FunctionStatus.Absent);
result.PostStatus.Should().Be(FunctionStatus.Absent);
result.Verdict.Should().Be(FunctionPatchVerdict.Inconclusive);
}
[Fact]
public void CfgDiffResult_StructureChanged_DetectsChange()
{
// Arrange
var diff = new CfgDiffResult
{
PreCfgHash = "hash1",
PostCfgHash = "hash2",
PreBlockCount = 5,
PostBlockCount = 6,
PreEdgeCount = 7,
PostEdgeCount = 9
};
// Assert
diff.StructureChanged.Should().BeTrue();
diff.BlockCountDelta.Should().Be(1);
diff.EdgeCountDelta.Should().Be(2);
}
[Fact]
public void CfgDiffResult_NoStructureChange_WhenHashesMatch()
{
// Arrange
var diff = new CfgDiffResult
{
PreCfgHash = "samehash",
PostCfgHash = "samehash",
PreBlockCount = 5,
PostBlockCount = 5,
PreEdgeCount = 7,
PostEdgeCount = 7
};
// Assert
diff.StructureChanged.Should().BeFalse();
diff.BlockCountDelta.Should().Be(0);
diff.EdgeCountDelta.Should().Be(0);
}
}
[Trait("Category", "Unit")]
public sealed class VulnerableEdgeDiffTests
{
[Fact]
public void Compute_AllEdgesRemoved_SetsFlag()
{
// Arrange
var preEdges = ImmutableArray.Create("bb0->bb1", "bb1->bb2");
var postEdges = ImmutableArray<string>.Empty;
// Act
var diff = VulnerableEdgeDiff.Compute(preEdges, postEdges);
// Assert
diff.AllVulnerableEdgesRemoved.Should().BeTrue();
diff.EdgesRemoved.Should().HaveCount(2);
diff.EdgesAdded.Should().BeEmpty();
}
[Fact]
public void Compute_SomeEdgesRemoved_SetsFlag()
{
// Arrange
var preEdges = ImmutableArray.Create("bb0->bb1", "bb1->bb2");
var postEdges = ImmutableArray.Create("bb0->bb1");
// Act
var diff = VulnerableEdgeDiff.Compute(preEdges, postEdges);
// Assert
diff.AllVulnerableEdgesRemoved.Should().BeFalse();
diff.SomeVulnerableEdgesRemoved.Should().BeTrue();
diff.EdgesRemoved.Should().Contain("bb1->bb2");
}
[Fact]
public void Compute_NoChange_NoEdgesRemovedOrAdded()
{
// Arrange
var edges = ImmutableArray.Create("bb0->bb1", "bb1->bb2");
// Act
var diff = VulnerableEdgeDiff.Compute(edges, edges);
// Assert
diff.NoChange.Should().BeTrue();
diff.EdgesRemoved.Should().BeEmpty();
diff.EdgesAdded.Should().BeEmpty();
}
[Fact]
public void Compute_EdgesAdded_TracksNewEdges()
{
// Arrange
var preEdges = ImmutableArray.Create("bb0->bb1");
var postEdges = ImmutableArray.Create("bb0->bb1", "bb1->bb3");
// Act
var diff = VulnerableEdgeDiff.Compute(preEdges, postEdges);
// Assert
diff.EdgesAdded.Should().Contain("bb1->bb3");
diff.AllVulnerableEdgesRemoved.Should().BeFalse();
}
[Fact]
public void Empty_ReturnsEmptyDiff()
{
// Act
var empty = VulnerableEdgeDiff.Empty;
// Assert
empty.EdgesInPre.Should().BeEmpty();
empty.EdgesInPost.Should().BeEmpty();
empty.EdgesRemoved.Should().BeEmpty();
empty.EdgesAdded.Should().BeEmpty();
}
}
[Trait("Category", "Unit")]
public sealed class SinkReachabilityDiffTests
{
[Fact]
public void Compute_AllSinksUnreachable_SetsFlag()
{
// Arrange
var preSinks = ImmutableArray.Create("memcpy", "strcpy");
var postSinks = ImmutableArray<string>.Empty;
// Act
var diff = SinkReachabilityDiff.Compute(preSinks, postSinks);
// Assert
diff.AllSinksUnreachable.Should().BeTrue();
diff.SinksMadeUnreachable.Should().HaveCount(2);
diff.SinksStillReachable.Should().BeEmpty();
}
[Fact]
public void Compute_SomeSinksUnreachable_SetsFlag()
{
// Arrange
var preSinks = ImmutableArray.Create("memcpy", "strcpy");
var postSinks = ImmutableArray.Create("memcpy");
// Act
var diff = SinkReachabilityDiff.Compute(preSinks, postSinks);
// Assert
diff.AllSinksUnreachable.Should().BeFalse();
diff.SomeSinksUnreachable.Should().BeTrue();
diff.SinksMadeUnreachable.Should().Contain("strcpy");
diff.SinksStillReachable.Should().Contain("memcpy");
}
[Fact]
public void Empty_ReturnsEmptyDiff()
{
// Act
var empty = SinkReachabilityDiff.Empty;
// Assert
empty.SinksReachableInPre.Should().BeEmpty();
empty.SinksReachableInPost.Should().BeEmpty();
empty.SinksMadeUnreachable.Should().BeEmpty();
empty.SinksStillReachable.Should().BeEmpty();
}
}