Gaps fill up, fixes, ui restructuring

This commit is contained in:
master
2026-02-19 22:10:54 +02:00
parent b5829dce5c
commit 04cacdca8a
331 changed files with 42859 additions and 2174 deletions

View File

@@ -0,0 +1,230 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Engine.Gates;
using StellaOps.Policy.Gates;
using Xunit;
namespace StellaOps.Policy.Engine.Tests.Gates;
/// <summary>
/// Tests for BeaconRateGate.
/// Sprint: SPRINT_20260219_014 (BEA-03)
/// </summary>
[Trait("Category", "Unit")]
[Trait("Sprint", "20260219.014")]
public sealed class BeaconRateGateTests
{
private readonly TimeProvider _fixedTimeProvider = new FixedTimeProvider(
new DateTimeOffset(2026, 2, 19, 14, 0, 0, TimeSpan.Zero));
#region Gate disabled
[Fact]
public async Task EvaluateAsync_WhenDisabled_ReturnsPass()
{
var gate = CreateGate(enabled: false);
var context = CreateContext("production");
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("disabled", result.Reason!);
}
#endregion
#region Environment filtering
[Fact]
public async Task EvaluateAsync_NonRequiredEnvironment_ReturnsPass()
{
var gate = CreateGate();
var context = CreateContext("development");
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("not required", result.Reason!);
}
#endregion
#region Missing beacon data
[Fact]
public async Task EvaluateAsync_MissingBeaconData_WarnMode_ReturnsPassWithWarning()
{
var gate = CreateGate(missingAction: PolicyGateDecisionType.Warn);
var context = CreateContext("production");
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("warn", result.Reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task EvaluateAsync_MissingBeaconData_BlockMode_ReturnsFail()
{
var gate = CreateGate(missingAction: PolicyGateDecisionType.Block);
var context = CreateContext("production");
var result = await gate.EvaluateAsync(context);
Assert.False(result.Passed);
}
#endregion
#region Rate threshold enforcement
[Fact]
public async Task EvaluateAsync_RateAboveThreshold_ReturnsPass()
{
var gate = CreateGate(minRate: 0.8);
var context = CreateContext("production", beaconRate: 0.95, beaconCount: 100);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("meets", result.Reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task EvaluateAsync_RateBelowThreshold_WarnMode_ReturnsPassWithWarning()
{
var gate = CreateGate(minRate: 0.8, belowAction: PolicyGateDecisionType.Warn);
var context = CreateContext("production", beaconRate: 0.5, beaconCount: 100);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("below threshold", result.Reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task EvaluateAsync_RateBelowThreshold_BlockMode_ReturnsFail()
{
var gate = CreateGate(minRate: 0.8, belowAction: PolicyGateDecisionType.Block);
var context = CreateContext("production", beaconRate: 0.5, beaconCount: 100);
var result = await gate.EvaluateAsync(context);
Assert.False(result.Passed);
}
#endregion
#region Minimum beacon count
[Fact]
public async Task EvaluateAsync_BelowMinBeaconCount_SkipsRateEnforcement()
{
var gate = CreateGate(minRate: 0.8, minBeaconCount: 50);
// Rate is bad but count is too low to enforce.
var context = CreateContext("production", beaconRate: 0.3, beaconCount: 5);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("deferred", result.Reason!, StringComparison.OrdinalIgnoreCase);
}
#endregion
#region Boundary conditions
[Fact]
public async Task EvaluateAsync_ExactlyAtThreshold_ReturnsPass()
{
var gate = CreateGate(minRate: 0.8);
var context = CreateContext("production", beaconRate: 0.8, beaconCount: 100);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
}
[Fact]
public async Task EvaluateAsync_JustBelowThreshold_TriggersAction()
{
var gate = CreateGate(minRate: 0.8, belowAction: PolicyGateDecisionType.Block);
var context = CreateContext("production", beaconRate: 0.7999, beaconCount: 100);
var result = await gate.EvaluateAsync(context);
Assert.False(result.Passed);
}
#endregion
#region Helpers
private BeaconRateGate CreateGate(
bool enabled = true,
double minRate = 0.8,
int minBeaconCount = 10,
PolicyGateDecisionType missingAction = PolicyGateDecisionType.Warn,
PolicyGateDecisionType belowAction = PolicyGateDecisionType.Warn)
{
var opts = new PolicyGateOptions
{
BeaconRate = new BeaconRateGateOptions
{
Enabled = enabled,
MinVerificationRate = minRate,
MinBeaconCount = minBeaconCount,
MissingBeaconAction = missingAction,
BelowThresholdAction = belowAction,
RequiredEnvironments = new List<string> { "production" },
},
};
var monitor = new StaticOptionsMonitor<PolicyGateOptions>(opts);
return new BeaconRateGate(monitor, NullLogger<BeaconRateGate>.Instance, _fixedTimeProvider);
}
private static PolicyGateContext CreateContext(
string environment,
double? beaconRate = null,
int? beaconCount = null)
{
var metadata = new Dictionary<string, string>();
if (beaconRate.HasValue)
{
metadata["beacon_verification_rate"] = beaconRate.Value.ToString(System.Globalization.CultureInfo.InvariantCulture);
}
if (beaconCount.HasValue)
{
metadata["beacon_verified_count"] = beaconCount.Value.ToString();
}
return new PolicyGateContext
{
Environment = environment,
SubjectKey = "test-subject",
Metadata = metadata,
};
}
private sealed class FixedTimeProvider : TimeProvider
{
private readonly DateTimeOffset _fixedTime;
public FixedTimeProvider(DateTimeOffset fixedTime) => _fixedTime = fixedTime;
public override DateTimeOffset GetUtcNow() => _fixedTime;
}
private sealed class StaticOptionsMonitor<T> : IOptionsMonitor<T>
{
private readonly T _value;
public StaticOptionsMonitor(T value) => _value = value;
public T CurrentValue => _value;
public T Get(string? name) => _value;
public IDisposable? OnChange(Action<T, string?> listener) => null;
}
#endregion
}

View File

@@ -0,0 +1,207 @@
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Policy.Engine.Gates;
using StellaOps.Policy.Gates;
using Xunit;
namespace StellaOps.Policy.Engine.Tests.Gates;
/// <summary>
/// Tests for ExecutionEvidenceGate.
/// Sprint: SPRINT_20260219_013 (SEE-03)
/// </summary>
[Trait("Category", "Unit")]
[Trait("Sprint", "20260219.013")]
public sealed class ExecutionEvidenceGateTests
{
private readonly TimeProvider _fixedTimeProvider = new FixedTimeProvider(
new DateTimeOffset(2026, 2, 19, 12, 0, 0, TimeSpan.Zero));
#region Gate disabled
[Fact]
public async Task EvaluateAsync_WhenDisabled_ReturnsPass()
{
var gate = CreateGate(enabled: false);
var context = CreateContext("production", hasEvidence: false);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("disabled", result.Reason!);
}
#endregion
#region Environment filtering
[Fact]
public async Task EvaluateAsync_NonRequiredEnvironment_ReturnsPass()
{
var gate = CreateGate();
var context = CreateContext("development", hasEvidence: false);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("not required", result.Reason!);
}
[Fact]
public async Task EvaluateAsync_RequiredEnvironment_EnforcesEvidence()
{
var gate = CreateGate(missingAction: PolicyGateDecisionType.Block);
var context = CreateContext("production", hasEvidence: false);
var result = await gate.EvaluateAsync(context);
Assert.False(result.Passed);
Assert.Contains("required", result.Reason!);
}
#endregion
#region Evidence present
[Fact]
public async Task EvaluateAsync_EvidencePresent_ReturnsPass()
{
var gate = CreateGate();
var context = CreateContext("production", hasEvidence: true);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("present", result.Reason!);
}
#endregion
#region Missing evidence actions
[Fact]
public async Task EvaluateAsync_MissingEvidence_WarnMode_ReturnsPassWithWarning()
{
var gate = CreateGate(missingAction: PolicyGateDecisionType.Warn);
var context = CreateContext("production", hasEvidence: false);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("warn", result.Reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task EvaluateAsync_MissingEvidence_BlockMode_ReturnsFail()
{
var gate = CreateGate(missingAction: PolicyGateDecisionType.Block);
var context = CreateContext("production", hasEvidence: false);
var result = await gate.EvaluateAsync(context);
Assert.False(result.Passed);
}
#endregion
#region Quality checks
[Fact]
public async Task EvaluateAsync_InsufficientHotSymbols_ReturnsPassWithWarning()
{
var gate = CreateGate(minHotSymbols: 10);
var context = CreateContext("production", hasEvidence: true, hotSymbolCount: 2);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("insufficient", result.Reason!, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task EvaluateAsync_SufficientHotSymbols_ReturnsCleanPass()
{
var gate = CreateGate(minHotSymbols: 3);
var context = CreateContext("production", hasEvidence: true, hotSymbolCount: 15);
var result = await gate.EvaluateAsync(context);
Assert.True(result.Passed);
Assert.Contains("meets", result.Reason!, StringComparison.OrdinalIgnoreCase);
}
#endregion
#region Helpers
private ExecutionEvidenceGate CreateGate(
bool enabled = true,
PolicyGateDecisionType missingAction = PolicyGateDecisionType.Warn,
int minHotSymbols = 3,
int minCallPaths = 1)
{
var opts = new PolicyGateOptions
{
ExecutionEvidence = new ExecutionEvidenceGateOptions
{
Enabled = enabled,
MissingEvidenceAction = missingAction,
MinHotSymbolCount = minHotSymbols,
MinUniqueCallPaths = minCallPaths,
RequiredEnvironments = new List<string> { "production" },
},
};
var monitor = new StaticOptionsMonitor<PolicyGateOptions>(opts);
return new ExecutionEvidenceGate(monitor, NullLogger<ExecutionEvidenceGate>.Instance, _fixedTimeProvider);
}
private static PolicyGateContext CreateContext(
string environment,
bool hasEvidence,
int? hotSymbolCount = null,
int? uniqueCallPaths = null)
{
var metadata = new Dictionary<string, string>();
if (hasEvidence)
{
metadata["has_execution_evidence"] = "true";
}
if (hotSymbolCount.HasValue)
{
metadata["execution_evidence_hot_symbol_count"] = hotSymbolCount.Value.ToString();
}
if (uniqueCallPaths.HasValue)
{
metadata["execution_evidence_unique_call_paths"] = uniqueCallPaths.Value.ToString();
}
return new PolicyGateContext
{
Environment = environment,
SubjectKey = "test-subject",
Metadata = metadata,
};
}
private sealed class FixedTimeProvider : TimeProvider
{
private readonly DateTimeOffset _fixedTime;
public FixedTimeProvider(DateTimeOffset fixedTime) => _fixedTime = fixedTime;
public override DateTimeOffset GetUtcNow() => _fixedTime;
}
private sealed class StaticOptionsMonitor<T> : IOptionsMonitor<T>
{
private readonly T _value;
public StaticOptionsMonitor(T value) => _value = value;
public T CurrentValue => _value;
public T Get(string? name) => _value;
public IDisposable? OnChange(Action<T, string?> listener) => null;
}
#endregion
}