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>
276 lines
8.2 KiB
C#
276 lines
8.2 KiB
C#
// 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 DiffEvidenceTests
|
|
{
|
|
[Fact]
|
|
public void FunctionRemoved_CreatesCorrectEvidence()
|
|
{
|
|
// Act
|
|
var evidence = DiffEvidence.FunctionRemoved("vuln_func");
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.FunctionRemoved);
|
|
evidence.FunctionName.Should().Be("vuln_func");
|
|
evidence.Weight.Should().Be(0.9m);
|
|
evidence.Description.Should().Contain("vuln_func");
|
|
}
|
|
|
|
[Fact]
|
|
public void FunctionRenamed_CreatesCorrectEvidence()
|
|
{
|
|
// Act
|
|
var evidence = DiffEvidence.FunctionRenamed("old_name", "new_name", 0.85m);
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.FunctionRenamed);
|
|
evidence.FunctionName.Should().Be("old_name");
|
|
evidence.Data["OldName"].Should().Be("old_name");
|
|
evidence.Data["NewName"].Should().Be("new_name");
|
|
evidence.Data["Similarity"].Should().Be("0.850");
|
|
}
|
|
|
|
[Fact]
|
|
public void CfgStructureChanged_CreatesCorrectEvidence()
|
|
{
|
|
// Act
|
|
var evidence = DiffEvidence.CfgStructureChanged("func", "hash1", "hash2");
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.CfgStructureChanged);
|
|
evidence.FunctionName.Should().Be("func");
|
|
evidence.Data["PreHash"].Should().Be("hash1");
|
|
evidence.Data["PostHash"].Should().Be("hash2");
|
|
evidence.Weight.Should().Be(0.5m);
|
|
}
|
|
|
|
[Fact]
|
|
public void VulnerableEdgeRemoved_CreatesCorrectEvidence()
|
|
{
|
|
// Arrange
|
|
var edges = ImmutableArray.Create("bb0->bb1", "bb1->bb2");
|
|
|
|
// Act
|
|
var evidence = DiffEvidence.VulnerableEdgeRemoved("func", edges);
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.VulnerableEdgeRemoved);
|
|
evidence.Weight.Should().Be(1.0m);
|
|
evidence.Data["EdgeCount"].Should().Be("2");
|
|
evidence.Data["EdgesRemoved"].Should().Contain("bb0->bb1");
|
|
}
|
|
|
|
[Fact]
|
|
public void SinkMadeUnreachable_CreatesCorrectEvidence()
|
|
{
|
|
// Arrange
|
|
var sinks = ImmutableArray.Create("memcpy", "strcpy");
|
|
|
|
// Act
|
|
var evidence = DiffEvidence.SinkMadeUnreachable("func", sinks);
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.SinkMadeUnreachable);
|
|
evidence.Weight.Should().Be(0.95m);
|
|
evidence.Data["SinkCount"].Should().Be("2");
|
|
}
|
|
|
|
[Fact]
|
|
public void TaintGateAdded_CreatesCorrectEvidence()
|
|
{
|
|
// Act
|
|
var evidence = DiffEvidence.TaintGateAdded("func", "BoundCheck", "len < bufsize");
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.TaintGateAdded);
|
|
evidence.Data["GateType"].Should().Be("BoundCheck");
|
|
evidence.Data["Condition"].Should().Be("len < bufsize");
|
|
evidence.Weight.Should().Be(0.85m);
|
|
}
|
|
|
|
[Fact]
|
|
public void SemanticDivergence_CreatesCorrectEvidence()
|
|
{
|
|
// Act
|
|
var evidence = DiffEvidence.SemanticDivergence("func", 0.45m);
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.SemanticDivergence);
|
|
evidence.Data["Similarity"].Should().Be("0.450");
|
|
evidence.Weight.Should().Be(0.6m);
|
|
}
|
|
|
|
[Fact]
|
|
public void IdenticalBinaries_CreatesCorrectEvidence()
|
|
{
|
|
// Act
|
|
var evidence = DiffEvidence.IdenticalBinaries("sha256:abc123");
|
|
|
|
// Assert
|
|
evidence.Type.Should().Be(DiffEvidenceType.IdenticalBinaries);
|
|
evidence.Data["Digest"].Should().Be("sha256:abc123");
|
|
evidence.Weight.Should().Be(1.0m);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(DiffEvidenceType.FunctionRemoved)]
|
|
[InlineData(DiffEvidenceType.FunctionRenamed)]
|
|
[InlineData(DiffEvidenceType.CfgStructureChanged)]
|
|
[InlineData(DiffEvidenceType.VulnerableEdgeRemoved)]
|
|
[InlineData(DiffEvidenceType.VulnerableBlockModified)]
|
|
[InlineData(DiffEvidenceType.SinkMadeUnreachable)]
|
|
[InlineData(DiffEvidenceType.TaintGateAdded)]
|
|
[InlineData(DiffEvidenceType.ConstantChanged)]
|
|
[InlineData(DiffEvidenceType.SemanticDivergence)]
|
|
[InlineData(DiffEvidenceType.IdenticalBinaries)]
|
|
public void DiffEvidenceType_AllValuesAreDefined(DiffEvidenceType type)
|
|
{
|
|
// Assert
|
|
Enum.IsDefined(type).Should().BeTrue();
|
|
}
|
|
}
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class DiffOptionsTests
|
|
{
|
|
[Fact]
|
|
public void Default_HasSensibleDefaults()
|
|
{
|
|
// Act
|
|
var options = DiffOptions.Default;
|
|
|
|
// Assert
|
|
options.IncludeSemanticAnalysis.Should().BeFalse();
|
|
options.IncludeReachabilityAnalysis.Should().BeTrue();
|
|
options.SemanticThreshold.Should().Be(0.85m);
|
|
options.FixedConfidenceThreshold.Should().Be(0.80m);
|
|
options.DetectRenames.Should().BeTrue();
|
|
options.FunctionTimeout.Should().Be(TimeSpan.FromSeconds(30));
|
|
options.TotalTimeout.Should().Be(TimeSpan.FromMinutes(10));
|
|
}
|
|
|
|
[Fact]
|
|
public void DiffOptions_CanBeCustomized()
|
|
{
|
|
// Act
|
|
var options = new DiffOptions
|
|
{
|
|
IncludeSemanticAnalysis = true,
|
|
SemanticThreshold = 0.95m,
|
|
DetectRenames = false
|
|
};
|
|
|
|
// Assert
|
|
options.IncludeSemanticAnalysis.Should().BeTrue();
|
|
options.SemanticThreshold.Should().Be(0.95m);
|
|
options.DetectRenames.Should().BeFalse();
|
|
}
|
|
}
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class DiffMetadataTests
|
|
{
|
|
[Fact]
|
|
public void CurrentEngineVersion_IsSet()
|
|
{
|
|
// Assert
|
|
DiffMetadata.CurrentEngineVersion.Should().NotBeNullOrEmpty();
|
|
DiffMetadata.CurrentEngineVersion.Should().Be("1.0.0");
|
|
}
|
|
|
|
[Fact]
|
|
public void DiffMetadata_StoresAllProperties()
|
|
{
|
|
// Arrange
|
|
var comparedAt = DateTimeOffset.UtcNow;
|
|
var duration = TimeSpan.FromSeconds(5);
|
|
var options = DiffOptions.Default;
|
|
|
|
// Act
|
|
var metadata = new DiffMetadata
|
|
{
|
|
ComparedAt = comparedAt,
|
|
EngineVersion = DiffMetadata.CurrentEngineVersion,
|
|
Duration = duration,
|
|
Options = options
|
|
};
|
|
|
|
// Assert
|
|
metadata.ComparedAt.Should().Be(comparedAt);
|
|
metadata.EngineVersion.Should().Be("1.0.0");
|
|
metadata.Duration.Should().Be(duration);
|
|
metadata.Options.Should().Be(options);
|
|
}
|
|
}
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class SingleBinaryCheckResultTests
|
|
{
|
|
[Fact]
|
|
public void NotVulnerable_CreatesCorrectResult()
|
|
{
|
|
// Arrange
|
|
var binaryDigest = "sha256:abc123";
|
|
var goldenSetId = "CVE-2024-1234";
|
|
var checkedAt = DateTimeOffset.UtcNow;
|
|
var duration = TimeSpan.FromMilliseconds(50);
|
|
|
|
// Act
|
|
var result = SingleBinaryCheckResult.NotVulnerable(
|
|
binaryDigest, goldenSetId, checkedAt, duration);
|
|
|
|
// Assert
|
|
result.IsVulnerable.Should().BeFalse();
|
|
result.Confidence.Should().Be(0.9m);
|
|
result.BinaryDigest.Should().Be(binaryDigest);
|
|
result.GoldenSetId.Should().Be(goldenSetId);
|
|
result.FunctionResults.Should().BeEmpty();
|
|
}
|
|
}
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class FunctionRenameTests
|
|
{
|
|
[Fact]
|
|
public void FunctionRename_StoresAllProperties()
|
|
{
|
|
// Act
|
|
var rename = new FunctionRename
|
|
{
|
|
OriginalName = "old_func",
|
|
NewName = "new_func",
|
|
Confidence = 0.92m,
|
|
Similarity = 0.92m
|
|
};
|
|
|
|
// Assert
|
|
rename.OriginalName.Should().Be("old_func");
|
|
rename.NewName.Should().Be("new_func");
|
|
rename.Confidence.Should().Be(0.92m);
|
|
rename.Similarity.Should().Be(0.92m);
|
|
}
|
|
}
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class RenameDetectionOptionsTests
|
|
{
|
|
[Fact]
|
|
public void Default_HasSensibleDefaults()
|
|
{
|
|
// Act
|
|
var options = RenameDetectionOptions.Default;
|
|
|
|
// Assert
|
|
options.MinSimilarity.Should().Be(0.7m);
|
|
options.UseCfgHash.Should().BeTrue();
|
|
options.UseBlockHashes.Should().BeTrue();
|
|
options.UseStringRefs.Should().BeTrue();
|
|
}
|
|
}
|