sprints and audit work

This commit is contained in:
StellaOps Bot
2026-01-07 09:36:16 +02:00
parent 05833e0af2
commit ab364c6032
377 changed files with 64534 additions and 1627 deletions

View File

@@ -0,0 +1,220 @@
// <copyright file="FacetQuotaGateTests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// </copyright>
// Sprint: SPRINT_20260105_002_003_FACET (QTA-014)
using System.Collections.Immutable;
using Microsoft.Extensions.Logging.Abstractions;
using Moq;
using StellaOps.Facet;
using StellaOps.Policy.Confidence.Models;
using StellaOps.Policy.Gates;
using StellaOps.Policy.TrustLattice;
using Xunit;
namespace StellaOps.Policy.Tests.Gates;
/// <summary>
/// Unit tests for <see cref="FacetQuotaGate"/> evaluation scenarios.
/// </summary>
[Trait("Category", "Unit")]
public sealed class FacetQuotaGateTests
{
private readonly Mock<IFacetDriftDetector> _driftDetectorMock;
private readonly FacetQuotaGateOptions _options;
private FacetQuotaGate _gate;
public FacetQuotaGateTests()
{
_driftDetectorMock = new Mock<IFacetDriftDetector>();
_options = new FacetQuotaGateOptions { Enabled = true };
_gate = CreateGate(_options);
}
private FacetQuotaGate CreateGate(FacetQuotaGateOptions options)
{
return new FacetQuotaGate(options, _driftDetectorMock.Object, NullLogger<FacetQuotaGate>.Instance);
}
[Fact]
public async Task EvaluateAsync_WhenDisabled_ReturnsPass()
{
// Arrange
var options = new FacetQuotaGateOptions { Enabled = false };
var gate = CreateGate(options);
var context = CreateContext();
var mergeResult = CreateMergeResult();
// Act
var result = await gate.EvaluateAsync(mergeResult, context);
// Assert
Assert.True(result.Passed);
Assert.Equal("Gate disabled", result.Reason);
}
[Fact]
public async Task EvaluateAsync_WhenNoSealAndNoSealActionIsPass_ReturnsPass()
{
// Arrange - no drift report in context means no seal
var options = new FacetQuotaGateOptions { Enabled = true, NoSealAction = NoSealAction.Pass };
var gate = CreateGate(options);
var context = CreateContext();
var mergeResult = CreateMergeResult();
// Act
var result = await gate.EvaluateAsync(mergeResult, context);
// Assert
Assert.True(result.Passed);
Assert.Contains("first scan", result.Reason);
}
[Fact]
public async Task EvaluateAsync_WhenNoSealAndNoSealActionIsWarn_ReturnsPassWithWarning()
{
// Arrange
var options = new FacetQuotaGateOptions { Enabled = true, NoSealAction = NoSealAction.Warn };
var gate = CreateGate(options);
var context = CreateContext();
var mergeResult = CreateMergeResult();
// Act
var result = await gate.EvaluateAsync(mergeResult, context);
// Assert
Assert.True(result.Passed);
Assert.Equal("no_baseline_seal", result.Reason);
Assert.True(result.Details.ContainsKey("action"));
}
[Fact]
public async Task EvaluateAsync_WhenNoSealAndNoSealActionIsBlock_ReturnsFail()
{
// Arrange
var options = new FacetQuotaGateOptions { Enabled = true, NoSealAction = NoSealAction.Block };
var gate = CreateGate(options);
var context = CreateContext();
var mergeResult = CreateMergeResult();
// Act
var result = await gate.EvaluateAsync(mergeResult, context);
// Assert
Assert.False(result.Passed);
Assert.Equal("no_baseline_seal", result.Reason);
}
[Fact]
public async Task EvaluateAsync_NullMergeResult_ThrowsArgumentNullException()
{
// Arrange
var context = CreateContext();
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() =>
_gate.EvaluateAsync(null!, context));
}
[Fact]
public async Task EvaluateAsync_NullContext_ThrowsArgumentNullException()
{
// Arrange
var mergeResult = CreateMergeResult();
// Act & Assert
await Assert.ThrowsAsync<ArgumentNullException>(() =>
_gate.EvaluateAsync(mergeResult, null!));
}
private static PolicyGateContext CreateContext()
{
return new PolicyGateContext
{
Environment = "test"
};
}
private static PolicyGateContext CreateContextWithDriftReportJson(string driftReportJson)
{
return new PolicyGateContext
{
Environment = "test",
Metadata = new Dictionary<string, string>
{
["FacetDriftReport"] = driftReportJson
}
};
}
private static MergeResult CreateMergeResult()
{
var emptyClaim = new ScoredClaim
{
SourceId = "test",
Status = VexStatus.NotAffected,
OriginalScore = 1.0,
AdjustedScore = 1.0,
ScopeSpecificity = 1,
Accepted = true,
Reason = "test"
};
return new MergeResult
{
Status = VexStatus.NotAffected,
Confidence = 0.9,
HasConflicts = false,
AllClaims = [emptyClaim],
WinningClaim = emptyClaim,
Conflicts = []
};
}
private static FacetDriftReport CreateDriftReport(QuotaVerdict verdict)
{
return new FacetDriftReport
{
ImageDigest = "sha256:abc123",
BaselineSealId = "seal-123",
AnalyzedAt = DateTimeOffset.UtcNow,
FacetDrifts = [CreateFacetDrift("test-facet", verdict)],
OverallVerdict = verdict
};
}
private static FacetDrift CreateFacetDrift(
string facetId,
QuotaVerdict verdict,
int baselineFileCount = 100)
{
// ChurnPercent is computed from TotalChanges / BaselineFileCount
// For different verdicts, we add files appropriately
var addedCount = verdict switch
{
QuotaVerdict.Warning => 10, // 10% churn
QuotaVerdict.Blocked => 30, // 30% churn
QuotaVerdict.RequiresVex => 50, // 50% churn
_ => 0
};
var addedFiles = Enumerable.Range(0, addedCount)
.Select(i => new FacetFileEntry(
$"/added/file{i}.txt",
$"sha256:added{i}",
100,
null))
.ToImmutableArray();
return new FacetDrift
{
FacetId = facetId,
Added = addedFiles,
Removed = [],
Modified = [],
DriftScore = addedCount,
QuotaVerdict = verdict,
BaselineFileCount = baselineFileCount
};
}
}