work work hard work
This commit is contained in:
@@ -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>());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user