old sprints work, new sprints for exposing functionality via cli, improve code_of_conduct and other agents instructions
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
// <copyright file="BinaryDiffEvidenceTests.cs" company="StellaOps">
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Sprint: SPRINT_20260112_008_LB_binary_diff_evidence_models (BINDIFF-LB-004)
|
||||
// </copyright>
|
||||
|
||||
using System;
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Extensions.Time.Testing;
|
||||
using StellaOps.TestKit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Evidence.Bundle.Tests;
|
||||
|
||||
[Trait("Category", TestCategories.Unit)]
|
||||
public sealed class BinaryDiffEvidenceTests
|
||||
{
|
||||
private readonly FakeTimeProvider _timeProvider = new(new DateTimeOffset(2026, 1, 15, 10, 30, 0, TimeSpan.Zero));
|
||||
|
||||
[Fact]
|
||||
public void Builder_WithBinaryDiff_IncludesInBundle()
|
||||
{
|
||||
// Arrange
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available,
|
||||
Hash = "sha256:abc123",
|
||||
PreviousBinaryDigest = "sha256:old123",
|
||||
CurrentBinaryDigest = "sha256:new456",
|
||||
DiffType = BinaryDiffType.Semantic,
|
||||
SimilarityScore = 0.95
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundle.BinaryDiff);
|
||||
Assert.Equal(EvidenceStatus.Available, bundle.BinaryDiff.Status);
|
||||
Assert.Equal("sha256:abc123", bundle.BinaryDiff.Hash);
|
||||
Assert.Equal(0.95, bundle.BinaryDiff.SimilarityScore);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeCompletenessScore_WithBinaryDiff_IncreasesScore()
|
||||
{
|
||||
// Arrange & Act - Bundle without binary diff
|
||||
var bundleWithout = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithReachability(new ReachabilityEvidence { Status = EvidenceStatus.Available })
|
||||
.Build();
|
||||
|
||||
// Bundle with binary diff
|
||||
var bundleWith = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-002")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithReachability(new ReachabilityEvidence { Status = EvidenceStatus.Available })
|
||||
.WithBinaryDiff(new BinaryDiffEvidence { Status = EvidenceStatus.Available })
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.True(bundleWith.ComputeCompletenessScore() > bundleWithout.ComputeCompletenessScore());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StatusSummary_IncludesBinaryDiffStatus()
|
||||
{
|
||||
// Arrange
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available,
|
||||
DiffType = BinaryDiffType.Semantic
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
var summary = bundle.CreateStatusSummary();
|
||||
Assert.Equal(EvidenceStatus.Available, summary.BinaryDiff);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryDiff_FunctionChanges_PreservesDeterministicOrder()
|
||||
{
|
||||
// Arrange - Create function changes in unsorted order
|
||||
var functionChanges = ImmutableArray.Create(
|
||||
new BinaryFunctionDiff
|
||||
{
|
||||
FunctionName = "z_function",
|
||||
Operation = BinaryDiffOperation.Modified,
|
||||
PreviousHash = "sha256:old1",
|
||||
CurrentHash = "sha256:new1"
|
||||
},
|
||||
new BinaryFunctionDiff
|
||||
{
|
||||
FunctionName = "a_function",
|
||||
Operation = BinaryDiffOperation.Added,
|
||||
CurrentHash = "sha256:new2"
|
||||
},
|
||||
new BinaryFunctionDiff
|
||||
{
|
||||
FunctionName = "m_function",
|
||||
Operation = BinaryDiffOperation.Removed,
|
||||
PreviousHash = "sha256:old3"
|
||||
}
|
||||
);
|
||||
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available,
|
||||
FunctionChanges = functionChanges.Sort((a, b) =>
|
||||
string.Compare(a.FunctionName, b.FunctionName, StringComparison.Ordinal))
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert - Should be sorted alphabetically
|
||||
Assert.Equal("a_function", bundle.BinaryDiff!.FunctionChanges[0].FunctionName);
|
||||
Assert.Equal("m_function", bundle.BinaryDiff.FunctionChanges[1].FunctionName);
|
||||
Assert.Equal("z_function", bundle.BinaryDiff.FunctionChanges[2].FunctionName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryDiff_SecurityChanges_CapturesMitigationChanges()
|
||||
{
|
||||
// Arrange
|
||||
var securityChanges = ImmutableArray.Create(
|
||||
new BinarySecurityChange
|
||||
{
|
||||
ChangeType = BinarySecurityChangeType.MitigationAdded,
|
||||
Description = "Stack canaries enabled",
|
||||
AffectedSymbols = ImmutableArray.Create("main", "process_input"),
|
||||
Severity = "info"
|
||||
},
|
||||
new BinarySecurityChange
|
||||
{
|
||||
ChangeType = BinarySecurityChangeType.MitigationRemoved,
|
||||
Description = "ASLR disabled",
|
||||
Severity = "high"
|
||||
}
|
||||
);
|
||||
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available,
|
||||
SecurityChanges = securityChanges
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, bundle.BinaryDiff!.SecurityChanges.Length);
|
||||
Assert.Contains(bundle.BinaryDiff.SecurityChanges,
|
||||
c => c.ChangeType == BinarySecurityChangeType.MitigationAdded);
|
||||
Assert.Contains(bundle.BinaryDiff.SecurityChanges,
|
||||
c => c.ChangeType == BinarySecurityChangeType.MitigationRemoved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryDiff_SemanticDiff_CapturesFingerprints()
|
||||
{
|
||||
// Arrange
|
||||
var semanticDiff = new BinarySemanticDiff
|
||||
{
|
||||
PreviousFingerprint = "fp:abc123",
|
||||
CurrentFingerprint = "fp:def456",
|
||||
SimilarityScore = 0.87,
|
||||
SemanticChanges = ImmutableArray.Create("control_flow_modified", "data_flow_changed")
|
||||
};
|
||||
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available,
|
||||
SemanticDiff = semanticDiff
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(bundle.BinaryDiff!.SemanticDiff);
|
||||
Assert.Equal(0.87, bundle.BinaryDiff.SemanticDiff.SimilarityScore);
|
||||
Assert.Equal(2, bundle.BinaryDiff.SemanticDiff.SemanticChanges.Length);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryDiff_Unavailable_CapturesReason()
|
||||
{
|
||||
// Arrange
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Unavailable,
|
||||
UnavailableReason = "No previous scan available for comparison"
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(EvidenceStatus.Unavailable, bundle.BinaryDiff!.Status);
|
||||
Assert.Equal("No previous scan available for comparison", bundle.BinaryDiff.UnavailableReason);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Hashes_IncludesBinaryDiffHash()
|
||||
{
|
||||
// Arrange
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available,
|
||||
Hash = "binarydiffhash123"
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Contains(bundle.Hashes.Hashes, h => h.Value == "binarydiffhash123");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryDiff_DiffTypes_AllSupported()
|
||||
{
|
||||
// Arrange & Act - Test all diff types
|
||||
var diffTypes = new[]
|
||||
{
|
||||
BinaryDiffType.None,
|
||||
BinaryDiffType.Structural,
|
||||
BinaryDiffType.Semantic,
|
||||
BinaryDiffType.Full
|
||||
};
|
||||
|
||||
foreach (var diffType in diffTypes)
|
||||
{
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available,
|
||||
DiffType = diffType
|
||||
};
|
||||
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId($"ALERT-{diffType}")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert
|
||||
Assert.Equal(diffType, bundle.BinaryDiff!.DiffType);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SchemaVersion_UpdatedForBinaryDiff()
|
||||
{
|
||||
// Arrange
|
||||
var binaryDiff = new BinaryDiffEvidence
|
||||
{
|
||||
Status = EvidenceStatus.Available
|
||||
};
|
||||
|
||||
// Act
|
||||
var bundle = new EvidenceBundleBuilder(_timeProvider)
|
||||
.WithAlertId("ALERT-001")
|
||||
.WithArtifactId("sha256:abc123")
|
||||
.WithBinaryDiff(binaryDiff)
|
||||
.Build();
|
||||
|
||||
// Assert - Schema version should be 1.1 or higher when binary diff is included
|
||||
Assert.True(
|
||||
bundle.SchemaVersion == "1.1" ||
|
||||
Version.Parse(bundle.SchemaVersion) >= Version.Parse("1.1"),
|
||||
$"Expected schema version >= 1.1, got {bundle.SchemaVersion}");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user