audit work, fixed StellaOps.sln warnings/errors, fixed tests, sprints work, new advisories

This commit is contained in:
master
2026-01-07 18:49:59 +02:00
parent 04ec098046
commit 608a7f85c0
866 changed files with 56323 additions and 6231 deletions

View File

@@ -0,0 +1,25 @@
# AGENTS - Facet Tests
## Roles
- QA / test engineer: deterministic tests for facet extraction, drift, and VEX workflows.
- Backend engineer: align tests with facet library contracts and fixtures.
## Required Reading
- docs/README.md
- docs/07_HIGH_LEVEL_ARCHITECTURE.md
- docs/modules/platform/architecture-overview.md
- src/__Libraries/AGENTS.md
- Current sprint file under docs/implplan/SPRINT_*.md
## Working Directory & Boundaries
- Primary scope: src/__Libraries/StellaOps.Facet.Tests
- Test target: src/__Libraries/StellaOps.Facet
- Avoid cross-module edits unless explicitly noted in the sprint file.
## Determinism and Safety
- Use fixed timestamps and deterministic file paths in test data.
- Avoid Guid.NewGuid in fixtures unless required for isolation.
## Testing
- Cover drift detection, merkle roots, extraction filters, and VEX draft workflows.
- Add negative cases for invalid inputs and ensure deterministic golden vectors.

View File

@@ -0,0 +1,302 @@
// <copyright file="FacetQuotaVexWorkflowE2ETests.cs" company="StellaOps">
// Copyright (c) StellaOps. Licensed under AGPL-3.0-or-later.
// </copyright>
// Sprint: SPRINT_20260105_002_003_FACET (QTA-024)
// Description: E2E test: Quota breach -> VEX draft -> approval workflow
using System.Collections.Immutable;
using Microsoft.Extensions.Time.Testing;
using Xunit;
namespace StellaOps.Facet.Tests;
/// <summary>
/// End-to-end tests for the facet quota breach to VEX approval workflow.
/// Tests the complete pipeline: quota breach detection -> VEX draft emission -> draft approval.
/// </summary>
[Trait("Category", "E2E")]
public sealed class FacetQuotaVexWorkflowE2ETests
{
private readonly FakeTimeProvider _timeProvider;
private readonly InMemoryFacetDriftVexDraftStore _draftStore;
private readonly FacetDriftVexEmitter _vexEmitter;
private readonly FacetDriftVexWorkflow _workflow;
public FacetQuotaVexWorkflowE2ETests()
{
_timeProvider = new FakeTimeProvider(new DateTimeOffset(2026, 1, 7, 12, 0, 0, TimeSpan.Zero));
_draftStore = new InMemoryFacetDriftVexDraftStore(_timeProvider);
_vexEmitter = new FacetDriftVexEmitter(FacetDriftVexEmitterOptions.Default, _timeProvider);
_workflow = new FacetDriftVexWorkflow(_vexEmitter, _draftStore);
}
[Fact]
public async Task E2E_QuotaBreach_GeneratesVexDraft_CanBeApproved()
{
// Arrange - Create a drift report with quota breach requiring VEX
var driftReport = CreateQuotaBreachingDriftReport(
imageDigest: "sha256:e2e-test-image",
facetId: "os-packages-dpkg",
churnPercent: 35m);
// Act - Step 1: Process drift and emit VEX drafts
var workflowResult = await _workflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
// Assert - Step 1: VEX draft was created
Assert.True(workflowResult.Success);
Assert.Equal(1, workflowResult.NewDraftsCreated);
Assert.Single(workflowResult.CreatedDraftIds);
var draftId = workflowResult.CreatedDraftIds[0];
// Verify draft was stored
var storedDraft = await _draftStore.FindByIdAsync(draftId, CancellationToken.None);
Assert.NotNull(storedDraft);
Assert.True(storedDraft.RequiresReview);
// Verify draft has pending status
var allDrafts = _draftStore.GetAllForTesting();
var draftWithReview = allDrafts.First(d => d.Draft.DraftId == draftId);
Assert.Equal(FacetDriftVexReviewStatus.Pending, draftWithReview.ReviewStatus);
// Act - Step 2: Approve the draft
var approvalResult = await _workflow.ApproveAsync(
draftId,
reviewedBy: "security-team@example.com",
notes: "Reviewed file changes, approved as authorized security patch",
CancellationToken.None);
// Assert - Step 2: Draft was approved
Assert.True(approvalResult);
var approvedDraftWrapper = _draftStore.GetAllForTesting().First(d => d.Draft.DraftId == draftId);
Assert.Equal(FacetDriftVexReviewStatus.Approved, approvedDraftWrapper.ReviewStatus);
Assert.Equal("security-team@example.com", approvedDraftWrapper.ReviewedBy);
}
[Fact]
public async Task E2E_MultipleFacetBreaches_AllGenerateDrafts()
{
// Arrange - Multiple facets exceeding quotas
var facetDrifts = new[]
{
CreateFacetDrift("os-packages-dpkg", QuotaVerdict.RequiresVex, 25m),
CreateFacetDrift("lang-deps-npm", QuotaVerdict.RequiresVex, 30m),
CreateFacetDrift("binaries-usr", QuotaVerdict.Ok, 2m) // OK, should not generate draft
};
var driftReport = new FacetDriftReport
{
ImageDigest = "sha256:multi-facet-test",
BaselineSealId = "seal-multi-001",
AnalyzedAt = _timeProvider.GetUtcNow(),
FacetDrifts = [.. facetDrifts],
OverallVerdict = QuotaVerdict.RequiresVex
};
// Act
var result = await _workflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
// Assert
Assert.True(result.Success);
Assert.Equal(2, result.NewDraftsCreated);
Assert.Equal(2, result.CreatedDraftIds.Length);
// Verify correct facets generated drafts
var allDrafts = _draftStore.GetAllForTesting();
Assert.Contains(allDrafts, d => d.Draft.FacetId == "os-packages-dpkg");
Assert.Contains(allDrafts, d => d.Draft.FacetId == "lang-deps-npm");
Assert.DoesNotContain(allDrafts, d => d.Draft.FacetId == "binaries-usr");
}
[Fact]
public async Task E2E_DraftRejection_MarksAsRejected()
{
// Arrange
var driftReport = CreateQuotaBreachingDriftReport(
imageDigest: "sha256:rejection-test",
facetId: "suspicious-facet",
churnPercent: 80m);
var workflowResult = await _workflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
var draftId = workflowResult.CreatedDraftIds[0];
// Act - Reject the draft
var result = await _workflow.RejectAsync(
draftId,
reviewedBy: "security-team@example.com",
reason: "Unauthorized change detected - requires investigation",
CancellationToken.None);
// Assert
Assert.True(result);
var rejectedDraft = _draftStore.GetAllForTesting().First(d => d.Draft.DraftId == draftId);
Assert.Equal(FacetDriftVexReviewStatus.Rejected, rejectedDraft.ReviewStatus);
Assert.Equal("security-team@example.com", rejectedDraft.ReviewedBy);
Assert.Contains("Unauthorized", rejectedDraft.ReviewNotes);
}
[Fact]
public async Task E2E_DraftExpiration_AfterTtl()
{
// Arrange - Use shorter TTL for testing
var shortTtlOptions = new FacetDriftVexEmitterOptions { DraftTtl = TimeSpan.FromDays(3) };
var shortTtlEmitter = new FacetDriftVexEmitter(shortTtlOptions, _timeProvider);
var shortTtlWorkflow = new FacetDriftVexWorkflow(shortTtlEmitter, _draftStore);
var driftReport = CreateQuotaBreachingDriftReport(
imageDigest: "sha256:expiry-test-short-ttl",
facetId: "test-facet",
churnPercent: 20m);
var workflowResult = await shortTtlWorkflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
var draftId = workflowResult.CreatedDraftIds[0];
// Get the draft's expiration time
var draft = await _draftStore.FindByIdAsync(draftId, CancellationToken.None);
Assert.NotNull(draft);
var expiresAt = draft.ExpiresAt;
// Act - Advance time past TTL (3 days + 1 day buffer)
_timeProvider.Advance(TimeSpan.FromDays(4));
// Assert - Draft's ExpiresAt should be before current time
var now = _timeProvider.GetUtcNow();
Assert.True(expiresAt < now, $"Draft should be expired: ExpiresAt={expiresAt}, Now={now}");
}
[Fact]
public async Task E2E_OverdueDrafts_CanBeQueried()
{
// Arrange - Use short review SLA for testing
var shortSlaOptions = new FacetDriftVexEmitterOptions { ReviewSlaDays = 2 };
var shortSlaEmitter = new FacetDriftVexEmitter(shortSlaOptions, _timeProvider);
var shortSlaWorkflow = new FacetDriftVexWorkflow(shortSlaEmitter, _draftStore);
var driftReport = CreateQuotaBreachingDriftReport(
imageDigest: "sha256:overdue-test-short-sla",
facetId: "overdue-facet",
churnPercent: 25m);
await shortSlaWorkflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
// Act - Advance time past review deadline (2 days + 1 day buffer)
_timeProvider.Advance(TimeSpan.FromDays(3));
// Query using the store directly with advanced time
var asOf = _timeProvider.GetUtcNow();
var overdueDrafts = await _draftStore.GetOverdueAsync(asOf, CancellationToken.None);
// Assert
Assert.Single(overdueDrafts);
Assert.Equal("sha256:overdue-test-short-sla", overdueDrafts[0].ImageDigest);
}
[Fact]
public async Task E2E_DraftContainsAuditTrail()
{
// Arrange
var driftReport = CreateQuotaBreachingDriftReport(
imageDigest: "sha256:audit-test",
facetId: "audited-facet",
churnPercent: 25m);
// Act
var workflowResult = await _workflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
var draftId = workflowResult.CreatedDraftIds[0];
var draft = await _draftStore.FindByIdAsync(draftId, CancellationToken.None);
// Assert
Assert.NotNull(draft);
Assert.Equal("sha256:audit-test", draft.ImageDigest);
Assert.NotNull(draft.DriftSummary);
Assert.Equal(25m, draft.DriftSummary.ChurnPercent);
Assert.NotEmpty(draft.EvidenceLinks);
Assert.Contains(draft.EvidenceLinks, l => l.Type == "facet_drift_analysis");
}
[Fact]
public async Task E2E_PendingDraftsCanBeQueried()
{
// Arrange - Create multiple drafts
var driftReport1 = CreateQuotaBreachingDriftReport("sha256:pending-test-image-001", "facet-1", 25m);
var driftReport2 = CreateQuotaBreachingDriftReport("sha256:pending-test-image-002", "facet-2", 30m);
await _workflow.ExecuteAsync(driftReport1, skipExisting: true, CancellationToken.None);
await _workflow.ExecuteAsync(driftReport2, skipExisting: true, CancellationToken.None);
// Act
var pendingDrafts = await _workflow.GetPendingDraftsAsync(ct: CancellationToken.None);
// Assert
Assert.Equal(2, pendingDrafts.Length);
}
[Fact]
public async Task E2E_SkipExistingDrafts_PreventsDuplicates()
{
// Arrange
var driftReport = CreateQuotaBreachingDriftReport(
imageDigest: "sha256:duplicate-test",
facetId: "test-facet",
churnPercent: 20m);
// Act - Execute workflow twice
var result1 = await _workflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
var result2 = await _workflow.ExecuteAsync(driftReport, skipExisting: true, CancellationToken.None);
// Assert
Assert.Equal(1, result1.NewDraftsCreated);
Assert.Equal(0, result2.NewDraftsCreated);
Assert.Equal(1, result2.ExistingDraftsSkipped);
}
#region Helper Methods
private FacetDriftReport CreateQuotaBreachingDriftReport(string imageDigest, string facetId, decimal churnPercent)
{
var addedCount = (int)churnPercent;
var addedFiles = Enumerable.Range(0, addedCount)
.Select(i => new FacetFileEntry($"/pkg/added{i}.deb", $"sha256:added{i}", 1024, null))
.ToImmutableArray();
var facetDrift = new FacetDrift
{
FacetId = facetId,
Added = addedFiles,
Removed = [],
Modified = [],
DriftScore = churnPercent,
QuotaVerdict = QuotaVerdict.RequiresVex,
BaselineFileCount = 100
};
return new FacetDriftReport
{
ImageDigest = imageDigest,
BaselineSealId = $"seal-{imageDigest.Substring(7, 8)}",
AnalyzedAt = _timeProvider.GetUtcNow(),
FacetDrifts = [facetDrift],
OverallVerdict = QuotaVerdict.RequiresVex
};
}
private static FacetDrift CreateFacetDrift(string facetId, QuotaVerdict verdict, decimal churnPercent)
{
var addedCount = (int)churnPercent;
var addedFiles = Enumerable.Range(0, addedCount)
.Select(i => new FacetFileEntry($"/{facetId}/file{i}", $"sha256:{facetId}{i}", 100, null))
.ToImmutableArray();
return new FacetDrift
{
FacetId = facetId,
Added = addedFiles,
Removed = [],
Modified = [],
DriftScore = churnPercent,
QuotaVerdict = verdict,
BaselineFileCount = 100
};
}
#endregion
}

View File

@@ -13,8 +13,6 @@
<ItemGroup>
<PackageReference Include="FluentAssertions" />
<PackageReference Include="Microsoft.Extensions.TimeProvider.Testing" />
<PackageReference Include="Microsoft.NET.Test.Sdk" />
<PackageReference Include="xunit.v3" />
</ItemGroup>
<ItemGroup>