269 lines
8.7 KiB
C#
269 lines
8.7 KiB
C#
// SPDX-License-Identifier: BUSL-1.1
|
|
// Copyright (c) 2025 StellaOps
|
|
// Sprint: SPRINT_20260112_004_BE_policy_determinization_attested_rules (DET-ATT-004)
|
|
// Task: Unit tests for VexProofGate anchor-aware mode
|
|
|
|
using System.Collections.Immutable;
|
|
using StellaOps.Policy.Gates;
|
|
using StellaOps.Policy.TrustLattice;
|
|
using VexStatus = StellaOps.Policy.Confidence.Models.VexStatus;
|
|
using Xunit;
|
|
|
|
namespace StellaOps.Policy.Tests.Gates;
|
|
|
|
public class VexProofGateTests
|
|
{
|
|
private static readonly DateTimeOffset FixedTime = new(2026, 1, 14, 12, 0, 0, TimeSpan.Zero);
|
|
|
|
private static MergeResult CreateMergeResult(VexStatus status) =>
|
|
new()
|
|
{
|
|
Status = status,
|
|
Confidence = 0.9,
|
|
HasConflicts = false,
|
|
AllClaims = ImmutableArray<ScoredClaim>.Empty,
|
|
WinningClaim = new ScoredClaim
|
|
{
|
|
SourceId = "test",
|
|
Status = status,
|
|
OriginalScore = 0.9,
|
|
AdjustedScore = 0.9,
|
|
ScopeSpecificity = 1,
|
|
Accepted = true,
|
|
Reason = "Test claim"
|
|
},
|
|
Conflicts = ImmutableArray<ConflictRecord>.Empty
|
|
};
|
|
|
|
[Fact]
|
|
public async Task EvaluateAsync_WhenDisabled_ReturnsPass()
|
|
{
|
|
// Arrange
|
|
var options = new VexProofGateOptions { Enabled = false };
|
|
var gate = new VexProofGate(options);
|
|
var mergeResult = CreateMergeResult(VexStatus.NotAffected);
|
|
var context = new PolicyGateContext { Environment = "production" };
|
|
|
|
// Act
|
|
var result = await gate.EvaluateAsync(mergeResult, context);
|
|
|
|
// Assert
|
|
Assert.True(result.Passed);
|
|
Assert.Equal("disabled", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EvaluateAsync_WhenAnchorAwareModeEnabled_RequiresAnchoring()
|
|
{
|
|
// Arrange
|
|
var options = new VexProofGateOptions
|
|
{
|
|
Enabled = true,
|
|
RequireProofForNotAffected = true,
|
|
AnchorAwareMode = true,
|
|
RequireVexAnchoring = true
|
|
};
|
|
var gate = new VexProofGate(options);
|
|
var mergeResult = CreateMergeResult(VexStatus.NotAffected);
|
|
var context = new PolicyGateContext
|
|
{
|
|
Environment = "production",
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
["vex_proof_id"] = "proof-123",
|
|
["vex_proof_confidence_tier"] = "high",
|
|
["vex_proof_anchored"] = "false" // Not anchored
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var result = await gate.EvaluateAsync(mergeResult, context);
|
|
|
|
// Assert
|
|
Assert.False(result.Passed);
|
|
Assert.Equal("vex_not_anchored", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EvaluateAsync_WhenAnchorAwareModeEnabled_PassesWithAnchoring()
|
|
{
|
|
// Arrange
|
|
var options = new VexProofGateOptions
|
|
{
|
|
Enabled = true,
|
|
RequireProofForNotAffected = true,
|
|
AnchorAwareMode = true,
|
|
RequireVexAnchoring = true,
|
|
RequireRekorVerification = false
|
|
};
|
|
var gate = new VexProofGate(options);
|
|
var mergeResult = CreateMergeResult(VexStatus.NotAffected);
|
|
var context = new PolicyGateContext
|
|
{
|
|
Environment = "production",
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
["vex_proof_id"] = "proof-123",
|
|
["vex_proof_confidence_tier"] = "high",
|
|
["vex_proof_anchored"] = "true",
|
|
["vex_proof_envelope_digest"] = "sha256:abc123"
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var result = await gate.EvaluateAsync(mergeResult, context);
|
|
|
|
// Assert
|
|
Assert.True(result.Passed);
|
|
Assert.Equal("proof_valid", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EvaluateAsync_WhenRekorRequired_FailsWithoutRekor()
|
|
{
|
|
// Arrange
|
|
var options = new VexProofGateOptions
|
|
{
|
|
Enabled = true,
|
|
RequireProofForNotAffected = true,
|
|
AnchorAwareMode = true,
|
|
RequireVexAnchoring = true,
|
|
RequireRekorVerification = true
|
|
};
|
|
var gate = new VexProofGate(options);
|
|
var mergeResult = CreateMergeResult(VexStatus.NotAffected);
|
|
var context = new PolicyGateContext
|
|
{
|
|
Environment = "production",
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
["vex_proof_id"] = "proof-123",
|
|
["vex_proof_confidence_tier"] = "high",
|
|
["vex_proof_anchored"] = "true",
|
|
["vex_proof_rekor_verified"] = "false"
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var result = await gate.EvaluateAsync(mergeResult, context);
|
|
|
|
// Assert
|
|
Assert.False(result.Passed);
|
|
Assert.Equal("rekor_verification_missing", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EvaluateAsync_WhenRekorRequired_PassesWithRekor()
|
|
{
|
|
// Arrange
|
|
var options = new VexProofGateOptions
|
|
{
|
|
Enabled = true,
|
|
RequireProofForNotAffected = true,
|
|
AnchorAwareMode = true,
|
|
RequireVexAnchoring = true,
|
|
RequireRekorVerification = true
|
|
};
|
|
var gate = new VexProofGate(options);
|
|
var mergeResult = CreateMergeResult(VexStatus.NotAffected);
|
|
var context = new PolicyGateContext
|
|
{
|
|
Environment = "production",
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
["vex_proof_id"] = "proof-123",
|
|
["vex_proof_confidence_tier"] = "high",
|
|
["vex_proof_anchored"] = "true",
|
|
["vex_proof_envelope_digest"] = "sha256:abc123",
|
|
["vex_proof_rekor_verified"] = "true",
|
|
["vex_proof_rekor_log_index"] = "12345678"
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var result = await gate.EvaluateAsync(mergeResult, context);
|
|
|
|
// Assert
|
|
Assert.True(result.Passed);
|
|
Assert.Equal("proof_valid", result.Reason);
|
|
Assert.True(result.Details.ContainsKey("rekorLogIndex"));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EvaluateAsync_StrictAnchorAware_EnforcesAllRequirements()
|
|
{
|
|
// Arrange
|
|
var options = VexProofGateOptions.StrictAnchorAware;
|
|
var gate = new VexProofGate(options);
|
|
var mergeResult = CreateMergeResult(VexStatus.NotAffected);
|
|
var context = new PolicyGateContext
|
|
{
|
|
Environment = "production",
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
["vex_proof_id"] = "proof-123",
|
|
["vex_proof_confidence_tier"] = "high",
|
|
["vex_proof_all_signed"] = "true",
|
|
["vex_proof_anchored"] = "true",
|
|
["vex_proof_envelope_digest"] = "sha256:abc123",
|
|
["vex_proof_rekor_verified"] = "true",
|
|
["vex_proof_rekor_log_index"] = "12345678"
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var result = await gate.EvaluateAsync(mergeResult, context);
|
|
|
|
// Assert
|
|
Assert.True(result.Passed);
|
|
Assert.Equal("proof_valid", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task EvaluateAsync_StrictAnchorAware_FailsWithoutSignedStatements()
|
|
{
|
|
// Arrange
|
|
var options = VexProofGateOptions.StrictAnchorAware;
|
|
var gate = new VexProofGate(options);
|
|
var mergeResult = CreateMergeResult(VexStatus.NotAffected);
|
|
var context = new PolicyGateContext
|
|
{
|
|
Environment = "production",
|
|
Metadata = new Dictionary<string, string>
|
|
{
|
|
["vex_proof_id"] = "proof-123",
|
|
["vex_proof_confidence_tier"] = "high",
|
|
["vex_proof_all_signed"] = "false", // Not signed
|
|
["vex_proof_anchored"] = "true",
|
|
["vex_proof_rekor_verified"] = "true"
|
|
}
|
|
};
|
|
|
|
// Act
|
|
var result = await gate.EvaluateAsync(mergeResult, context);
|
|
|
|
// Assert
|
|
Assert.False(result.Passed);
|
|
Assert.Equal("unsigned_statements", result.Reason);
|
|
}
|
|
|
|
[Fact]
|
|
public void StrictAnchorAware_HasExpectedDefaults()
|
|
{
|
|
// Act
|
|
var options = VexProofGateOptions.StrictAnchorAware;
|
|
|
|
// Assert
|
|
Assert.True(options.Enabled);
|
|
Assert.Equal("high", options.MinimumConfidenceTier);
|
|
Assert.True(options.RequireProofForNotAffected);
|
|
Assert.True(options.RequireProofForFixed);
|
|
Assert.True(options.RequireSignedStatements);
|
|
Assert.True(options.AnchorAwareMode);
|
|
Assert.True(options.RequireVexAnchoring);
|
|
Assert.True(options.RequireRekorVerification);
|
|
Assert.Equal(0, options.MaxAllowedConflicts);
|
|
Assert.Equal(72, options.MaxProofAgeHours);
|
|
}
|
|
}
|