partly or unimplemented features - now implemented
This commit is contained in:
@@ -0,0 +1,535 @@
|
||||
// Licensed to StellaOps under the BUSL-1.1 license.
|
||||
|
||||
using FluentAssertions;
|
||||
using StellaOps.VexLens.Consensus;
|
||||
using StellaOps.VexLens.Models;
|
||||
using StellaOps.VexLens.Trust;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.VexLens.Tests.Consensus;
|
||||
|
||||
/// <summary>
|
||||
/// Truth table tests for VEX lattice merge correctness.
|
||||
/// Validates all combinations of VEX status pairs produce correct merge results.
|
||||
/// </summary>
|
||||
[Trait("Category", "Unit")]
|
||||
[Trait("Feature", "VexLattice")]
|
||||
public class VexLatticeTruthTableTests
|
||||
{
|
||||
private readonly VexConsensusEngine _engine;
|
||||
private readonly ConsensusConfiguration _config;
|
||||
|
||||
public VexLatticeTruthTableTests()
|
||||
{
|
||||
_config = VexConsensusEngine.CreateDefaultConfiguration();
|
||||
_engine = new VexConsensusEngine(_config);
|
||||
}
|
||||
|
||||
#region Lattice Order Truth Table
|
||||
|
||||
/// <summary>
|
||||
/// Verifies the lattice order: Affected < UnderInvestigation < Fixed < NotAffected
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(VexStatus.Affected, 0)]
|
||||
[InlineData(VexStatus.UnderInvestigation, 1)]
|
||||
[InlineData(VexStatus.Fixed, 2)]
|
||||
[InlineData(VexStatus.NotAffected, 3)]
|
||||
public void StatusLattice_OrderIsCorrect(VexStatus status, int expectedOrder)
|
||||
{
|
||||
// Act
|
||||
var order = _config.StatusLattice.StatusOrder[status];
|
||||
|
||||
// Assert
|
||||
order.Should().Be(expectedOrder);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StatusLattice_BottomIsAffected()
|
||||
{
|
||||
_config.StatusLattice.BottomStatus.Should().Be(VexStatus.Affected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StatusLattice_TopIsNotAffected()
|
||||
{
|
||||
_config.StatusLattice.TopStatus.Should().Be(VexStatus.NotAffected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Two-Statement Lattice Merge Truth Table
|
||||
|
||||
/// <summary>
|
||||
/// Complete truth table for lattice consensus with two statements.
|
||||
/// Expected behavior: lattice consensus selects the most conservative (lowest) status.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
// Same status pairs - should return that status
|
||||
[InlineData(VexStatus.Affected, VexStatus.Affected, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.UnderInvestigation, VexStatus.UnderInvestigation, VexStatus.UnderInvestigation)]
|
||||
[InlineData(VexStatus.Fixed, VexStatus.Fixed, VexStatus.Fixed)]
|
||||
[InlineData(VexStatus.NotAffected, VexStatus.NotAffected, VexStatus.NotAffected)]
|
||||
// Affected vs others - Affected always wins (most conservative)
|
||||
[InlineData(VexStatus.Affected, VexStatus.UnderInvestigation, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.Affected, VexStatus.Fixed, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.Affected, VexStatus.NotAffected, VexStatus.Affected)]
|
||||
// UnderInvestigation vs Fixed/NotAffected - UnderInvestigation wins
|
||||
[InlineData(VexStatus.UnderInvestigation, VexStatus.Fixed, VexStatus.UnderInvestigation)]
|
||||
[InlineData(VexStatus.UnderInvestigation, VexStatus.NotAffected, VexStatus.UnderInvestigation)]
|
||||
// Fixed vs NotAffected - Fixed wins (more conservative)
|
||||
[InlineData(VexStatus.Fixed, VexStatus.NotAffected, VexStatus.Fixed)]
|
||||
// Reverse order to verify commutativity
|
||||
[InlineData(VexStatus.UnderInvestigation, VexStatus.Affected, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.Fixed, VexStatus.Affected, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.NotAffected, VexStatus.Affected, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.Fixed, VexStatus.UnderInvestigation, VexStatus.UnderInvestigation)]
|
||||
[InlineData(VexStatus.NotAffected, VexStatus.UnderInvestigation, VexStatus.UnderInvestigation)]
|
||||
[InlineData(VexStatus.NotAffected, VexStatus.Fixed, VexStatus.Fixed)]
|
||||
public async Task LatticeConsensus_TwoStatements_SelectsMostConservative(
|
||||
VexStatus status1,
|
||||
VexStatus status2,
|
||||
VexStatus expectedConsensus)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var statements = new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status1, 0.5, now),
|
||||
CreateWeightedStatement("stmt-2", status2, 0.5, now)
|
||||
};
|
||||
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", statements, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.ConsensusStatus.Should().Be(expectedConsensus,
|
||||
because: $"lattice merge of {status1} and {status2} should yield {expectedConsensus}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies lattice consensus is commutative: merge(A, B) == merge(B, A)
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(VexStatus.Affected, VexStatus.NotAffected)]
|
||||
[InlineData(VexStatus.UnderInvestigation, VexStatus.Fixed)]
|
||||
[InlineData(VexStatus.Fixed, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.NotAffected, VexStatus.UnderInvestigation)]
|
||||
public async Task LatticeConsensus_IsCommutative(VexStatus status1, VexStatus status2)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
var request1 = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status1, 0.5, now),
|
||||
CreateWeightedStatement("stmt-2", status2, 0.5, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
var request2 = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status2, 0.5, now),
|
||||
CreateWeightedStatement("stmt-2", status1, 0.5, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result1 = await _engine.ComputeConsensusAsync(request1);
|
||||
var result2 = await _engine.ComputeConsensusAsync(request2);
|
||||
|
||||
// Assert
|
||||
result1.ConsensusStatus.Should().Be(result2.ConsensusStatus,
|
||||
because: "lattice merge should be commutative");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies lattice consensus is associative: merge(merge(A, B), C) == merge(A, merge(B, C))
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(VexStatus.Affected, VexStatus.Fixed, VexStatus.NotAffected, VexStatus.Affected)]
|
||||
[InlineData(VexStatus.UnderInvestigation, VexStatus.Fixed, VexStatus.NotAffected, VexStatus.UnderInvestigation)]
|
||||
[InlineData(VexStatus.Fixed, VexStatus.NotAffected, VexStatus.NotAffected, VexStatus.Fixed)]
|
||||
public async Task LatticeConsensus_IsAssociative(
|
||||
VexStatus status1,
|
||||
VexStatus status2,
|
||||
VexStatus status3,
|
||||
VexStatus expected)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status1, 0.33, now),
|
||||
CreateWeightedStatement("stmt-2", status2, 0.33, now),
|
||||
CreateWeightedStatement("stmt-3", status3, 0.34, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.ConsensusStatus.Should().Be(expected,
|
||||
because: "lattice merge should be associative");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies lattice consensus is idempotent: merge(A, A) == A
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(VexStatus.Affected)]
|
||||
[InlineData(VexStatus.UnderInvestigation)]
|
||||
[InlineData(VexStatus.Fixed)]
|
||||
[InlineData(VexStatus.NotAffected)]
|
||||
public async Task LatticeConsensus_IsIdempotent(VexStatus status)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status, 0.5, now),
|
||||
CreateWeightedStatement("stmt-2", status, 0.5, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.ConsensusStatus.Should().Be(status,
|
||||
because: "lattice merge should be idempotent");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Weighted Vote Truth Table
|
||||
|
||||
/// <summary>
|
||||
/// Truth table for weighted vote consensus - majority wins.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
// Clear majorities
|
||||
[InlineData(0.7, VexStatus.Affected, 0.3, VexStatus.NotAffected, VexStatus.Affected)]
|
||||
[InlineData(0.3, VexStatus.Affected, 0.7, VexStatus.NotAffected, VexStatus.NotAffected)]
|
||||
[InlineData(0.6, VexStatus.Fixed, 0.4, VexStatus.UnderInvestigation, VexStatus.Fixed)]
|
||||
// Ties resolved by weight order
|
||||
[InlineData(0.5, VexStatus.Affected, 0.5, VexStatus.NotAffected, VexStatus.Affected)]
|
||||
public async Task WeightedVote_SelectsStatusWithHighestTotalWeight(
|
||||
double weight1,
|
||||
VexStatus status1,
|
||||
double weight2,
|
||||
VexStatus status2,
|
||||
VexStatus expected)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status1, weight1, now),
|
||||
CreateWeightedStatement("stmt-2", status2, weight2, now)
|
||||
}, ConsensusMode.WeightedVote);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.ConsensusStatus.Should().Be(expected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Weighted vote with multiple statements per status.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
// Two affected (0.3+0.3=0.6) vs one not_affected (0.4) -> affected wins
|
||||
[InlineData(VexStatus.Affected)]
|
||||
public async Task WeightedVote_AggregatesWeightsByStatus(VexStatus expected)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", VexStatus.Affected, 0.3, now),
|
||||
CreateWeightedStatement("stmt-2", VexStatus.Affected, 0.3, now),
|
||||
CreateWeightedStatement("stmt-3", VexStatus.NotAffected, 0.4, now)
|
||||
}, ConsensusMode.WeightedVote);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.ConsensusStatus.Should().Be(expected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Highest Weight Truth Table
|
||||
|
||||
/// <summary>
|
||||
/// Truth table for highest weight consensus - single highest weight wins.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(0.9, VexStatus.NotAffected, 0.1, VexStatus.Affected, VexStatus.NotAffected)]
|
||||
[InlineData(0.1, VexStatus.NotAffected, 0.9, VexStatus.Affected, VexStatus.Affected)]
|
||||
[InlineData(0.5, VexStatus.Fixed, 0.4, VexStatus.UnderInvestigation, VexStatus.Fixed)]
|
||||
public async Task HighestWeight_SelectsStatementWithMaxWeight(
|
||||
double weight1,
|
||||
VexStatus status1,
|
||||
double weight2,
|
||||
VexStatus status2,
|
||||
VexStatus expected)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status1, weight1, now),
|
||||
CreateWeightedStatement("stmt-2", status2, weight2, now)
|
||||
}, ConsensusMode.HighestWeight);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.ConsensusStatus.Should().Be(expected);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Conflict Detection Truth Table
|
||||
|
||||
/// <summary>
|
||||
/// Verifies conflicts are detected when statements disagree.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(VexStatus.Affected, VexStatus.NotAffected, true)]
|
||||
[InlineData(VexStatus.Affected, VexStatus.Affected, false)]
|
||||
[InlineData(VexStatus.Fixed, VexStatus.UnderInvestigation, true)]
|
||||
[InlineData(VexStatus.NotAffected, VexStatus.NotAffected, false)]
|
||||
public async Task ConflictDetection_IdentifiesDisagreements(
|
||||
VexStatus status1,
|
||||
VexStatus status2,
|
||||
bool expectConflict)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status1, 0.5, now),
|
||||
CreateWeightedStatement("stmt-2", status2, 0.5, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
if (expectConflict)
|
||||
{
|
||||
result.Conflicts.Should().NotBeNullOrEmpty("conflicts should be detected");
|
||||
}
|
||||
else
|
||||
{
|
||||
(result.Conflicts ?? Array.Empty<ConsensusConflict>()).Should().BeEmpty("no conflicts expected");
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Outcome Classification Truth Table
|
||||
|
||||
/// <summary>
|
||||
/// Verifies outcome is classified correctly based on agreement.
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData(VexStatus.Affected, VexStatus.Affected, ConsensusOutcome.Unanimous)]
|
||||
[InlineData(VexStatus.NotAffected, VexStatus.NotAffected, ConsensusOutcome.Unanimous)]
|
||||
public async Task OutcomeClassification_UnanimousWhenAllAgree(
|
||||
VexStatus status1,
|
||||
VexStatus status2,
|
||||
ConsensusOutcome expected)
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", status1, 0.5, now),
|
||||
CreateWeightedStatement("stmt-2", status2, 0.5, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Outcome.Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task OutcomeClassification_ConflictResolvedWhenDisagree()
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", VexStatus.Affected, 0.5, now),
|
||||
CreateWeightedStatement("stmt-2", VexStatus.NotAffected, 0.5, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Outcome.Should().Be(ConsensusOutcome.ConflictResolved);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Edge Cases
|
||||
|
||||
[Fact]
|
||||
public async Task SingleStatement_ReturnsItsStatus()
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", VexStatus.Fixed, 0.8, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.ConsensusStatus.Should().Be(VexStatus.Fixed);
|
||||
result.Outcome.Should().Be(ConsensusOutcome.Unanimous);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task EmptyStatements_ReturnsNoDataOutcome()
|
||||
{
|
||||
// Arrange
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0",
|
||||
new List<WeightedStatement>(), ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Outcome.Should().Be(ConsensusOutcome.NoData);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AllBelowThreshold_ReturnsNoData()
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", VexStatus.Affected, 0.05, now), // Below 0.1 threshold
|
||||
CreateWeightedStatement("stmt-2", VexStatus.Fixed, 0.05, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result.Outcome.Should().Be(ConsensusOutcome.NoData);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Determinism Tests
|
||||
|
||||
[Fact]
|
||||
public async Task Consensus_IsDeterministic_SameInputSameOutput()
|
||||
{
|
||||
// Arrange
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
var request = CreateRequest("CVE-2024-1234", "pkg:test@1.0", new List<WeightedStatement>
|
||||
{
|
||||
CreateWeightedStatement("stmt-1", VexStatus.Affected, 0.6, now),
|
||||
CreateWeightedStatement("stmt-2", VexStatus.NotAffected, 0.4, now)
|
||||
}, ConsensusMode.Lattice);
|
||||
|
||||
// Act
|
||||
var result1 = await _engine.ComputeConsensusAsync(request);
|
||||
var result2 = await _engine.ComputeConsensusAsync(request);
|
||||
|
||||
// Assert
|
||||
result1.ConsensusStatus.Should().Be(result2.ConsensusStatus);
|
||||
result1.ConfidenceScore.Should().Be(result2.ConfidenceScore);
|
||||
result1.Outcome.Should().Be(result2.Outcome);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Helpers
|
||||
|
||||
private static WeightedStatement CreateWeightedStatement(
|
||||
string id,
|
||||
VexStatus status,
|
||||
double weight,
|
||||
DateTimeOffset timestamp)
|
||||
{
|
||||
var statement = new NormalizedStatement(
|
||||
StatementId: id,
|
||||
VulnerabilityId: "CVE-2024-1234",
|
||||
VulnerabilityAliases: null,
|
||||
Product: new NormalizedProduct("pkg:test@1.0", "Test", "1.0", "pkg:test@1.0", null, null),
|
||||
Status: status,
|
||||
StatusNotes: null,
|
||||
Justification: status == VexStatus.NotAffected ? VexJustification.VulnerableCodeNotPresent : null,
|
||||
ImpactStatement: null,
|
||||
ActionStatement: null,
|
||||
ActionStatementTimestamp: null,
|
||||
Versions: null,
|
||||
Subcomponents: null,
|
||||
FirstSeen: timestamp,
|
||||
LastSeen: timestamp);
|
||||
|
||||
var breakdown = new TrustWeightBreakdown(
|
||||
IssuerWeight: weight * 0.4,
|
||||
SignatureWeight: weight * 0.2,
|
||||
FreshnessWeight: weight * 0.2,
|
||||
SourceFormatWeight: weight * 0.1,
|
||||
StatusSpecificityWeight: weight * 0.1,
|
||||
CustomWeight: 0.0);
|
||||
|
||||
var trustWeight = new TrustWeightResult(
|
||||
Statement: statement,
|
||||
Weight: weight,
|
||||
Breakdown: breakdown,
|
||||
Factors: new List<TrustWeightFactor>(),
|
||||
Warnings: new List<string>());
|
||||
|
||||
return new WeightedStatement(
|
||||
Statement: statement,
|
||||
Weight: trustWeight,
|
||||
Issuer: new VexIssuer("issuer-1", "Test Issuer", IssuerCategory.Community, TrustTier.Trusted, null),
|
||||
SourceDocumentId: "doc-1");
|
||||
}
|
||||
|
||||
private static VexConsensusRequest CreateRequest(
|
||||
string vulnerabilityId,
|
||||
string productKey,
|
||||
IReadOnlyList<WeightedStatement> statements,
|
||||
ConsensusMode mode)
|
||||
{
|
||||
var policy = new ConsensusPolicy(
|
||||
Mode: mode,
|
||||
MinimumWeightThreshold: 0.1,
|
||||
ConflictThreshold: 0.3,
|
||||
RequireJustificationForNotAffected: false,
|
||||
PreferredIssuers: null);
|
||||
|
||||
var context = new ConsensusContext(
|
||||
TenantId: "test-tenant",
|
||||
EvaluationTime: DateTimeOffset.UtcNow,
|
||||
Policy: policy);
|
||||
|
||||
return new VexConsensusRequest(
|
||||
VulnerabilityId: vulnerabilityId,
|
||||
ProductKey: productKey,
|
||||
Statements: statements,
|
||||
Context: context);
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user