work work hard work

This commit is contained in:
StellaOps Bot
2025-12-18 00:47:24 +02:00
parent dee252940b
commit b4235c134c
189 changed files with 9627 additions and 3258 deletions

View File

@@ -13,6 +13,8 @@ using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options;
using StellaOps.Auth.Abstractions;
using StellaOps.Policy;
using StellaOps.Scanner.Storage.Models;
using StellaOps.Scanner.Storage.Services;
using StellaOps.Scanner.WebService.Contracts;
using StellaOps.Scanner.WebService.Options;
using StellaOps.Scanner.WebService.Services;
@@ -30,7 +32,8 @@ public sealed class ReportEventDispatcherTests
public async Task PublishAsync_EmitsReportReadyAndScanCompleted()
{
var publisher = new RecordingEventPublisher();
var dispatcher = new ReportEventDispatcher(publisher, Microsoft.Extensions.Options.Options.Create(new ScannerWebServiceOptions()), TimeProvider.System, NullLogger<ReportEventDispatcher>.Instance);
var tracker = new RecordingClassificationChangeTracker();
var dispatcher = new ReportEventDispatcher(publisher, tracker, Microsoft.Extensions.Options.Options.Create(new ScannerWebServiceOptions()), TimeProvider.System, NullLogger<ReportEventDispatcher>.Instance);
var cancellationToken = CancellationToken.None;
var request = new ReportRequestDto
@@ -165,6 +168,143 @@ public sealed class ReportEventDispatcherTests
Assert.Equal("blocked", scanPayload.Report.Verdict);
}
[Fact]
public async Task PublishAsync_RecordsFnDriftClassificationChanges()
{
var publisher = new RecordingEventPublisher();
var tracker = new RecordingClassificationChangeTracker();
var dispatcher = new ReportEventDispatcher(publisher, tracker, Microsoft.Extensions.Options.Options.Create(new ScannerWebServiceOptions()), TimeProvider.System, NullLogger<ReportEventDispatcher>.Instance);
var cancellationToken = CancellationToken.None;
var request = new ReportRequestDto
{
ImageDigest = "sha256:feedface",
Findings = new[]
{
new PolicyPreviewFindingDto
{
Id = "finding-1",
Severity = "Critical",
Repository = "acme/edge/api",
Cve = "CVE-2024-9999",
Purl = "pkg:nuget/Acme.Edge.Api@1.2.3",
Tags = new[] { "reachability:runtime" }
}
}
};
var baseline = new PolicyVerdict("finding-1", PolicyVerdictStatus.Pass, ConfigVersion: "1.0");
var projected = new PolicyVerdict(
"finding-1",
PolicyVerdictStatus.Blocked,
Score: 47.5,
ConfigVersion: "1.0",
SourceTrust: "NVD",
Reachability: "runtime");
var preview = new PolicyPreviewResponse(
Success: true,
PolicyDigest: "digest-123",
RevisionId: "rev-42",
Issues: ImmutableArray<PolicyIssue>.Empty,
Diffs: ImmutableArray.Create(new PolicyVerdictDiff(baseline, projected)),
ChangedCount: 1);
var document = new ReportDocumentDto
{
ReportId = "report-abc",
ImageDigest = "sha256:feedface",
GeneratedAt = DateTimeOffset.Parse("2025-10-19T12:34:56Z"),
Verdict = "blocked",
Policy = new ReportPolicyDto
{
RevisionId = "rev-42",
Digest = "digest-123"
},
Summary = new ReportSummaryDto
{
Total = 1,
Blocked = 1,
Warned = 0,
Ignored = 0,
Quieted = 0
}
};
var context = new DefaultHttpContext();
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(StellaOpsClaimTypes.Tenant, "tenant-alpha") }));
await dispatcher.PublishAsync(request, preview, document, envelope: null, context, cancellationToken);
var change = Assert.Single(tracker.Changes);
Assert.Equal("sha256:feedface", change.ArtifactDigest);
Assert.Equal("CVE-2024-9999", change.VulnId);
Assert.Equal("pkg:nuget/Acme.Edge.Api@1.2.3", change.PackagePurl);
Assert.Equal(ClassificationStatus.Unaffected, change.PreviousStatus);
Assert.Equal(ClassificationStatus.Affected, change.NewStatus);
Assert.Equal(DriftCause.ReachabilityDelta, change.Cause);
Assert.Equal(document.GeneratedAt, change.ChangedAt);
Assert.NotEqual(Guid.Empty, change.TenantId);
Assert.NotEqual(Guid.Empty, change.ExecutionId);
Assert.NotEqual(Guid.Empty, change.ManifestId);
}
[Fact]
public async Task PublishAsync_DoesNotFailWhenFnDriftTrackingThrows()
{
var publisher = new RecordingEventPublisher();
var tracker = new RecordingClassificationChangeTracker
{
ThrowOnTrack = true
};
var dispatcher = new ReportEventDispatcher(publisher, tracker, Microsoft.Extensions.Options.Options.Create(new ScannerWebServiceOptions()), TimeProvider.System, NullLogger<ReportEventDispatcher>.Instance);
var cancellationToken = CancellationToken.None;
var request = new ReportRequestDto
{
ImageDigest = "sha256:feedface",
Findings = new[]
{
new PolicyPreviewFindingDto
{
Id = "finding-1",
Severity = "Critical",
Repository = "acme/edge/api",
Cve = "CVE-2024-9999",
Purl = "pkg:nuget/Acme.Edge.Api@1.2.3"
}
}
};
var baseline = new PolicyVerdict("finding-1", PolicyVerdictStatus.Pass, ConfigVersion: "1.0");
var projected = new PolicyVerdict("finding-1", PolicyVerdictStatus.Blocked, ConfigVersion: "1.0");
var preview = new PolicyPreviewResponse(
Success: true,
PolicyDigest: "digest-123",
RevisionId: "rev-42",
Issues: ImmutableArray<PolicyIssue>.Empty,
Diffs: ImmutableArray.Create(new PolicyVerdictDiff(baseline, projected)),
ChangedCount: 1);
var document = new ReportDocumentDto
{
ReportId = "report-abc",
ImageDigest = "sha256:feedface",
GeneratedAt = DateTimeOffset.Parse("2025-10-19T12:34:56Z"),
Verdict = "blocked",
Policy = new ReportPolicyDto(),
Summary = new ReportSummaryDto()
};
var context = new DefaultHttpContext();
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(StellaOpsClaimTypes.Tenant, "tenant-alpha") }));
await dispatcher.PublishAsync(request, preview, document, envelope: null, context, cancellationToken);
Assert.Equal(2, publisher.Events.Count);
}
[Fact]
public async Task PublishAsync_HonoursConfiguredConsoleAndApiSegments()
{
@@ -186,7 +326,8 @@ public sealed class ReportEventDispatcherTests
});
var publisher = new RecordingEventPublisher();
var dispatcher = new ReportEventDispatcher(publisher, options, TimeProvider.System, NullLogger<ReportEventDispatcher>.Instance);
var tracker = new RecordingClassificationChangeTracker();
var dispatcher = new ReportEventDispatcher(publisher, tracker, options, TimeProvider.System, NullLogger<ReportEventDispatcher>.Instance);
var cancellationToken = CancellationToken.None;
var request = new ReportRequestDto
@@ -295,4 +436,40 @@ public sealed class ReportEventDispatcherTests
return Task.CompletedTask;
}
}
private sealed class RecordingClassificationChangeTracker : IClassificationChangeTracker
{
public List<ClassificationChange> Changes { get; } = new();
public bool ThrowOnTrack { get; init; }
public Task TrackChangeAsync(ClassificationChange change, CancellationToken cancellationToken = default)
{
if (ThrowOnTrack)
{
throw new InvalidOperationException("Tracking failure");
}
Changes.Add(change);
return Task.CompletedTask;
}
public Task TrackChangesAsync(IEnumerable<ClassificationChange> changes, CancellationToken cancellationToken = default)
{
if (ThrowOnTrack)
{
throw new InvalidOperationException("Tracking failure");
}
Changes.AddRange(changes);
return Task.CompletedTask;
}
public Task<IReadOnlyList<ClassificationChange>> ComputeDeltaAsync(
Guid tenantId,
string artifactDigest,
Guid previousExecutionId,
Guid currentExecutionId,
CancellationToken cancellationToken = default)
=> Task.FromResult<IReadOnlyList<ClassificationChange>>(Array.Empty<ClassificationChange>());
}
}