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:
@@ -0,0 +1,22 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<IsPackable>false</IsPackable>
|
||||
<NoWarn>$(NoWarn);xUnit1051</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Moq" />
|
||||
<PackageReference Include="FluentAssertions" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\StellaOps.Attestor.FixChain\StellaOps.Attestor.FixChain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,158 @@
|
||||
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.FixChain.Tests.Unit;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class FixChainPredicateTests
|
||||
{
|
||||
[Fact]
|
||||
public void PredicateType_IsCorrect()
|
||||
{
|
||||
// Assert
|
||||
FixChainPredicate.PredicateType.Should().Be("https://stella-ops.org/predicates/fix-chain/v1");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FixChainPredicate_CanBeCreated()
|
||||
{
|
||||
// Arrange & Act
|
||||
var predicate = CreateValidPredicate();
|
||||
|
||||
// Assert
|
||||
predicate.CveId.Should().Be("CVE-2024-1234");
|
||||
predicate.Component.Should().Be("openssl");
|
||||
predicate.GoldenSetRef.Digest.Should().StartWith("sha256:");
|
||||
predicate.VulnerableBinary.Sha256.Should().HaveLength(64);
|
||||
predicate.PatchedBinary.Sha256.Should().HaveLength(64);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(FixChainVerdict.StatusFixed)]
|
||||
[InlineData(FixChainVerdict.StatusPartial)]
|
||||
[InlineData(FixChainVerdict.StatusNotFixed)]
|
||||
[InlineData(FixChainVerdict.StatusInconclusive)]
|
||||
public void FixChainVerdict_StatusConstants_AreDefined(string status)
|
||||
{
|
||||
// Assert
|
||||
status.Should().NotBeNullOrEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentRef_StoresDigestAndUri()
|
||||
{
|
||||
// Arrange & Act
|
||||
var contentRef = new ContentRef("sha256:abc123", "https://example.com/artifact");
|
||||
|
||||
// Assert
|
||||
contentRef.Digest.Should().Be("sha256:abc123");
|
||||
contentRef.Uri.Should().Be("https://example.com/artifact");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ContentRef_UriIsOptional()
|
||||
{
|
||||
// Arrange & Act
|
||||
var contentRef = new ContentRef("sha256:abc123");
|
||||
|
||||
// Assert
|
||||
contentRef.Uri.Should().BeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BinaryRef_StoresAllProperties()
|
||||
{
|
||||
// Arrange & Act
|
||||
var binaryRef = new BinaryRef(
|
||||
"abcd1234" + new string('0', 56),
|
||||
"x86_64",
|
||||
"build-12345",
|
||||
"pkg:generic/openssl@3.0.0");
|
||||
|
||||
// Assert
|
||||
binaryRef.Sha256.Should().HaveLength(64);
|
||||
binaryRef.Architecture.Should().Be("x86_64");
|
||||
binaryRef.BuildId.Should().Be("build-12345");
|
||||
binaryRef.Purl.Should().Be("pkg:generic/openssl@3.0.0");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SignatureDiffSummary_StoresCounts()
|
||||
{
|
||||
// Arrange & Act
|
||||
var summary = new SignatureDiffSummary(
|
||||
VulnerableFunctionsRemoved: 2,
|
||||
VulnerableFunctionsModified: 3,
|
||||
VulnerableEdgesEliminated: 5,
|
||||
SanitizersInserted: 1,
|
||||
Details: ["Function foo removed", "Edge bb0->bb1 eliminated"]);
|
||||
|
||||
// Assert
|
||||
summary.VulnerableFunctionsRemoved.Should().Be(2);
|
||||
summary.VulnerableFunctionsModified.Should().Be(3);
|
||||
summary.VulnerableEdgesEliminated.Should().Be(5);
|
||||
summary.SanitizersInserted.Should().Be(1);
|
||||
summary.Details.Should().HaveCount(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReachabilityOutcome_StoresPathCounts()
|
||||
{
|
||||
// Arrange & Act
|
||||
var outcome = new ReachabilityOutcome(
|
||||
PrePathCount: 5,
|
||||
PostPathCount: 0,
|
||||
Eliminated: true,
|
||||
Reason: "All paths eliminated");
|
||||
|
||||
// Assert
|
||||
outcome.PrePathCount.Should().Be(5);
|
||||
outcome.PostPathCount.Should().Be(0);
|
||||
outcome.Eliminated.Should().BeTrue();
|
||||
outcome.Reason.Should().Be("All paths eliminated");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalyzerMetadata_StoresAllProperties()
|
||||
{
|
||||
// Arrange & Act
|
||||
var metadata = new AnalyzerMetadata(
|
||||
"StellaOps.BinaryIndex",
|
||||
"1.0.0",
|
||||
"sha256:sourcedigest");
|
||||
|
||||
// Assert
|
||||
metadata.Name.Should().Be("StellaOps.BinaryIndex");
|
||||
metadata.Version.Should().Be("1.0.0");
|
||||
metadata.SourceDigest.Should().Be("sha256:sourcedigest");
|
||||
}
|
||||
|
||||
private static FixChainPredicate CreateValidPredicate()
|
||||
{
|
||||
return new FixChainPredicate
|
||||
{
|
||||
CveId = "CVE-2024-1234",
|
||||
Component = "openssl",
|
||||
GoldenSetRef = new ContentRef("sha256:goldenset123"),
|
||||
SbomRef = new ContentRef("sha256:sbom456"),
|
||||
VulnerableBinary = new BinaryRef(
|
||||
new string('a', 64),
|
||||
"x86_64",
|
||||
"build-pre",
|
||||
null),
|
||||
PatchedBinary = new BinaryRef(
|
||||
new string('b', 64),
|
||||
"x86_64",
|
||||
"build-post",
|
||||
"pkg:generic/openssl@3.0.1"),
|
||||
SignatureDiff = new SignatureDiffSummary(1, 2, 3, 0, []),
|
||||
Reachability = new ReachabilityOutcome(5, 0, true, "All paths eliminated"),
|
||||
Verdict = new FixChainVerdict(FixChainVerdict.StatusFixed, 0.95m, ["Vulnerability fixed"]),
|
||||
Analyzer = new AnalyzerMetadata("Test", "1.0.0", "sha256:test"),
|
||||
AnalyzedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.FixChain.Tests.Unit;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class FixChainStatementBuilderTests
|
||||
{
|
||||
private readonly FixChainStatementBuilder _builder;
|
||||
private readonly Mock<TimeProvider> _timeProvider;
|
||||
private readonly DateTimeOffset _fixedTime = new(2026, 1, 10, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
public FixChainStatementBuilderTests()
|
||||
{
|
||||
_timeProvider = new Mock<TimeProvider>();
|
||||
_timeProvider.Setup(t => t.GetUtcNow()).Returns(_fixedTime);
|
||||
|
||||
var options = Options.Create(new FixChainOptions
|
||||
{
|
||||
AnalyzerName = "TestAnalyzer",
|
||||
AnalyzerVersion = "1.0.0",
|
||||
AnalyzerSourceDigest = "sha256:testsource"
|
||||
});
|
||||
|
||||
_builder = new FixChainStatementBuilder(
|
||||
_timeProvider.Object,
|
||||
options,
|
||||
NullLogger<FixChainStatementBuilder>.Instance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_CreatesValidStatement()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Should().NotBeNull();
|
||||
result.Statement.Should().NotBeNull();
|
||||
result.Predicate.Should().NotBeNull();
|
||||
result.ContentDigest.Should().NotBeNullOrEmpty();
|
||||
result.ContentDigest.Should().HaveLength(64); // SHA-256 hex
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_SetsCorrectCveAndComponent()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.CveId.Should().Be("CVE-2024-1234");
|
||||
result.Predicate.Component.Should().Be("openssl");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_FormatsDigestsWithPrefix()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.GoldenSetRef.Digest.Should().StartWith("sha256:");
|
||||
result.Predicate.SbomRef.Digest.Should().StartWith("sha256:");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_SetsBinaryReferences()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.VulnerableBinary.Sha256.Should().Be(request.VulnerableBinary.Sha256);
|
||||
result.Predicate.VulnerableBinary.Architecture.Should().Be("x86_64");
|
||||
result.Predicate.PatchedBinary.Sha256.Should().Be(request.PatchedBinary.Sha256);
|
||||
result.Predicate.PatchedBinary.Purl.Should().Be(request.ComponentPurl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_SetsAnalyzerMetadata()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.Analyzer.Name.Should().Be("TestAnalyzer");
|
||||
result.Predicate.Analyzer.Version.Should().Be("1.0.0");
|
||||
result.Predicate.Analyzer.SourceDigest.Should().Be("sha256:testsource");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_SetsAnalyzedAtTimestamp()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.AnalyzedAt.Should().Be(_fixedTime);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_BuildsSignatureDiffSummary()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
request = request with
|
||||
{
|
||||
DiffResult = request.DiffResult with
|
||||
{
|
||||
FunctionsRemoved = 2,
|
||||
FunctionsModified = 3,
|
||||
EdgesEliminated = 5,
|
||||
TaintGatesAdded = 1
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.SignatureDiff.VulnerableFunctionsRemoved.Should().Be(2);
|
||||
result.Predicate.SignatureDiff.VulnerableFunctionsModified.Should().Be(3);
|
||||
result.Predicate.SignatureDiff.VulnerableEdgesEliminated.Should().Be(5);
|
||||
result.Predicate.SignatureDiff.SanitizersInserted.Should().Be(1);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_BuildsReachabilityOutcome()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
request = request with
|
||||
{
|
||||
DiffResult = request.DiffResult with
|
||||
{
|
||||
PrePathCount = 5,
|
||||
PostPathCount = 0
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.Reachability.PrePathCount.Should().Be(5);
|
||||
result.Predicate.Reachability.PostPathCount.Should().Be(0);
|
||||
result.Predicate.Reachability.Eliminated.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("Fixed", 0.90, "fixed")]
|
||||
[InlineData("PartialFix", 0.70, "partial")]
|
||||
[InlineData("StillVulnerable", 0.20, "not_fixed")]
|
||||
[InlineData("Inconclusive", 0.30, "inconclusive")]
|
||||
public async Task BuildAsync_SetsCorrectVerdictStatus(string inputVerdict, decimal confidence, string expectedStatus)
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
request = request with
|
||||
{
|
||||
DiffResult = request.DiffResult with
|
||||
{
|
||||
Verdict = inputVerdict,
|
||||
Confidence = confidence
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.Verdict.Status.Should().Be(expectedStatus);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_IncludesRationaleForFunctionsRemoved()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
request = request with
|
||||
{
|
||||
DiffResult = request.DiffResult with
|
||||
{
|
||||
FunctionsRemoved = 2
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.Verdict.Rationale.Should().Contain(r => r.Contains("2") && r.Contains("removed"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_IncludesRationaleForPathsEliminated()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
request = request with
|
||||
{
|
||||
DiffResult = request.DiffResult with
|
||||
{
|
||||
PrePathCount = 5,
|
||||
PostPathCount = 0
|
||||
}
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Predicate.Verdict.Rationale.Should().Contain(r => r.Contains("path") && r.Contains("eliminated"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_SetsStatementSubject()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Statement.Subject.Should().HaveCount(1);
|
||||
result.Statement.Subject[0].Name.Should().Be(request.ComponentPurl);
|
||||
result.Statement.Subject[0].Digest["sha256"].Should().Be(request.PatchedBinary.Sha256);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task BuildAsync_ContentDigestIsDeterministic()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateValidRequest();
|
||||
|
||||
// Act
|
||||
var result1 = await _builder.BuildAsync(request);
|
||||
var result2 = await _builder.BuildAsync(request);
|
||||
|
||||
// Assert
|
||||
result1.ContentDigest.Should().Be(result2.ContentDigest);
|
||||
}
|
||||
|
||||
private static FixChainBuildRequest CreateValidRequest()
|
||||
{
|
||||
return new FixChainBuildRequest
|
||||
{
|
||||
CveId = "CVE-2024-1234",
|
||||
Component = "openssl",
|
||||
GoldenSetDigest = "goldenset123",
|
||||
SbomDigest = "sbom456",
|
||||
ComponentPurl = "pkg:generic/openssl@3.0.1",
|
||||
VulnerableBinary = new BinaryIdentity
|
||||
{
|
||||
Sha256 = new string('a', 64),
|
||||
Architecture = "x86_64",
|
||||
BuildId = "build-pre"
|
||||
},
|
||||
PatchedBinary = new BinaryIdentity
|
||||
{
|
||||
Sha256 = new string('b', 64),
|
||||
Architecture = "x86_64",
|
||||
BuildId = "build-post"
|
||||
},
|
||||
DiffResult = new PatchDiffInput
|
||||
{
|
||||
Verdict = "Fixed",
|
||||
Confidence = 0.95m,
|
||||
FunctionsRemoved = 1,
|
||||
FunctionsModified = 0,
|
||||
EdgesEliminated = 3,
|
||||
TaintGatesAdded = 0,
|
||||
PrePathCount = 5,
|
||||
PostPathCount = 0,
|
||||
Evidence = ["Edge bb0->bb1 eliminated"]
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,310 @@
|
||||
// Licensed under AGPL-3.0-or-later. Copyright (C) 2026 StellaOps Contributors.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using FluentAssertions;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Attestor.FixChain.Tests.Unit;
|
||||
|
||||
[Trait("Category", "Unit")]
|
||||
public sealed class FixChainValidatorTests
|
||||
{
|
||||
private readonly FixChainValidator _validator = new();
|
||||
|
||||
[Fact]
|
||||
public void Validate_ValidPredicate_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate();
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
result.Errors.Should().BeEmpty();
|
||||
result.Predicate.Should().Be(predicate);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MissingCveId_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with { CveId = "" };
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("cveId"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidCveIdFormat_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with { CveId = "INVALID-1234" };
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("CVE-"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MissingComponent_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with { Component = "" };
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("component"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MissingGoldenSetDigest_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
GoldenSetRef = new ContentRef("")
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("goldenSetRef.digest"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidDigestFormat_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
GoldenSetRef = new ContentRef("invaliddigest")
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("algorithm"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidBinarySha256Length_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
VulnerableBinary = new BinaryRef("short", "x86_64", null, null)
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("sha256") && e.Contains("64"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MissingArchitecture_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
PatchedBinary = new BinaryRef(new string('a', 64), "", null, null)
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("architecture"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidVerdictStatus_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
Verdict = new FixChainVerdict("invalid_status", 0.9m, [])
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("status"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_InvalidConfidence_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
Verdict = new FixChainVerdict(FixChainVerdict.StatusFixed, 1.5m, [])
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("confidence"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MissingAnalyzerName_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
Analyzer = new AnalyzerMetadata("", "1.0.0", "sha256:source")
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("analyzer.name"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_DefaultTimestamp_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with { AnalyzedAt = default };
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().Contain(e => e.Contains("analyzedAt"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateJson_ValidJson_ReturnsSuccess()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate();
|
||||
var json = JsonSerializer.Serialize(predicate);
|
||||
var element = JsonDocument.Parse(json).RootElement;
|
||||
|
||||
// Act
|
||||
var result = _validator.ValidateJson(element);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateJson_InvalidJson_ReturnsError()
|
||||
{
|
||||
// Arrange
|
||||
var json = JsonDocument.Parse("{}").RootElement;
|
||||
|
||||
// Act
|
||||
var result = _validator.ValidateJson(json);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ValidateJson_MalformedJson_ReturnsParseError()
|
||||
{
|
||||
// Arrange
|
||||
var json = JsonDocument.Parse("{\"cveId\": 12345}").RootElement;
|
||||
|
||||
// Act
|
||||
var result = _validator.ValidateJson(json);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(FixChainVerdict.StatusFixed)]
|
||||
[InlineData(FixChainVerdict.StatusPartial)]
|
||||
[InlineData(FixChainVerdict.StatusNotFixed)]
|
||||
[InlineData(FixChainVerdict.StatusInconclusive)]
|
||||
public void Validate_AllValidStatusValues_AreAccepted(string status)
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
Verdict = new FixChainVerdict(status, 0.5m, [])
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_MultipleErrors_ReturnsAll()
|
||||
{
|
||||
// Arrange
|
||||
var predicate = CreateValidPredicate() with
|
||||
{
|
||||
CveId = "",
|
||||
Component = "",
|
||||
GoldenSetRef = new ContentRef("")
|
||||
};
|
||||
|
||||
// Act
|
||||
var result = _validator.Validate(predicate);
|
||||
|
||||
// Assert
|
||||
result.IsValid.Should().BeFalse();
|
||||
result.Errors.Should().HaveCountGreaterThan(1);
|
||||
}
|
||||
|
||||
private static FixChainPredicate CreateValidPredicate()
|
||||
{
|
||||
return new FixChainPredicate
|
||||
{
|
||||
CveId = "CVE-2024-1234",
|
||||
Component = "openssl",
|
||||
GoldenSetRef = new ContentRef("sha256:goldenset123"),
|
||||
SbomRef = new ContentRef("sha256:sbom456"),
|
||||
VulnerableBinary = new BinaryRef(
|
||||
new string('a', 64),
|
||||
"x86_64",
|
||||
"build-pre",
|
||||
null),
|
||||
PatchedBinary = new BinaryRef(
|
||||
new string('b', 64),
|
||||
"x86_64",
|
||||
"build-post",
|
||||
"pkg:generic/openssl@3.0.1"),
|
||||
SignatureDiff = new SignatureDiffSummary(1, 2, 3, 0, []),
|
||||
Reachability = new ReachabilityOutcome(5, 0, true, "All paths eliminated"),
|
||||
Verdict = new FixChainVerdict(FixChainVerdict.StatusFixed, 0.95m, ["Vulnerability fixed"]),
|
||||
Analyzer = new AnalyzerMetadata("Test", "1.0.0", "sha256:test"),
|
||||
AnalyzedAt = DateTimeOffset.UtcNow
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user