Complete batch 012 (golden set diff) and 013 (advisory chat), fix build errors

Sprints completed:
- SPRINT_20260110_012_* (golden set diff layer - 10 sprints)
- SPRINT_20260110_013_* (advisory chat - 4 sprints)

Build fixes applied:
- Fix namespace conflicts with Microsoft.Extensions.Options.Options.Create
- Fix VexDecisionReachabilityIntegrationTests API drift (major rewrite)
- Fix VexSchemaValidationTests FluentAssertions method name
- Fix FixChainGateIntegrationTests ambiguous type references
- Fix AdvisoryAI test files required properties and namespace aliases
- Add stub types for CveMappingController (ICveSymbolMappingService)
- Fix VerdictBuilderService static context issue

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
master
2026-01-11 10:09:07 +02:00
parent a3b2f30a11
commit 7f7eb8b228
232 changed files with 58979 additions and 91 deletions

View File

@@ -0,0 +1,244 @@
// Licensed under AGPL-3.0-or-later. 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();
}
}