Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,171 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Auth.Abstractions;
|
||||
using StellaOps.Policy;
|
||||
using StellaOps.Scanner.WebService.Contracts;
|
||||
using StellaOps.Scanner.WebService.Services;
|
||||
|
||||
namespace StellaOps.Scanner.WebService.Tests;
|
||||
|
||||
public sealed class ReportEventDispatcherTests
|
||||
{
|
||||
private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web)
|
||||
{
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public async Task PublishAsync_EmitsReportReadyAndScanCompleted()
|
||||
{
|
||||
var publisher = new RecordingEventPublisher();
|
||||
var dispatcher = new ReportEventDispatcher(publisher, 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",
|
||||
Tags = new[] { "reachability:runtime", "kev:CVE-2024-9999" }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
},
|
||||
Verdicts = new[]
|
||||
{
|
||||
new PolicyPreviewVerdictDto
|
||||
{
|
||||
FindingId = "finding-1",
|
||||
Status = "Blocked",
|
||||
Score = 47.5,
|
||||
SourceTrust = "NVD",
|
||||
Reachability = "runtime"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var envelope = new DsseEnvelopeDto
|
||||
{
|
||||
PayloadType = "application/vnd.stellaops.report+json",
|
||||
Payload = Convert.ToBase64String(JsonSerializer.SerializeToUtf8Bytes(document, SerializerOptions)),
|
||||
Signatures = new[]
|
||||
{
|
||||
new DsseSignatureDto { KeyId = "test-key", Algorithm = "hs256", Signature = "signature-value" }
|
||||
}
|
||||
};
|
||||
|
||||
var context = new DefaultHttpContext();
|
||||
context.User = new ClaimsPrincipal(new ClaimsIdentity(new[]
|
||||
{
|
||||
new Claim(StellaOpsClaimTypes.Tenant, "tenant-alpha")
|
||||
}));
|
||||
context.Request.Scheme = "https";
|
||||
context.Request.Host = new HostString("scanner.example");
|
||||
|
||||
await dispatcher.PublishAsync(request, preview, document, envelope, context, cancellationToken);
|
||||
|
||||
Assert.Equal(2, publisher.Events.Count);
|
||||
|
||||
var readyEvent = Assert.Single(publisher.Events, evt => evt.Kind == OrchestratorEventKinds.ScannerReportReady);
|
||||
Assert.Equal("tenant-alpha", readyEvent.Tenant);
|
||||
Assert.Equal("scanner.event.report.ready:tenant-alpha:report-abc", readyEvent.IdempotencyKey);
|
||||
Assert.Equal("api", readyEvent.Scope?.Repo);
|
||||
Assert.Equal("acme/edge", readyEvent.Scope?.Namespace);
|
||||
Assert.Equal("sha256:feedface", readyEvent.Scope?.Digest);
|
||||
var readyPayload = Assert.IsType<ReportReadyEventPayload>(readyEvent.Payload);
|
||||
Assert.Equal("report-abc", readyPayload.ReportId);
|
||||
Assert.Equal("report-abc", readyPayload.ScanId);
|
||||
Assert.Equal("fail", readyPayload.Verdict);
|
||||
Assert.Equal(0, readyPayload.QuietedFindingCount);
|
||||
Assert.NotNull(readyPayload.Delta);
|
||||
Assert.Equal(1, readyPayload.Delta?.NewCritical);
|
||||
Assert.Contains("CVE-2024-9999", readyPayload.Delta?.Kev ?? Array.Empty<string>());
|
||||
Assert.Equal("https://scanner.example/ui/reports/report-abc", readyPayload.Links.Ui);
|
||||
Assert.Equal("https://scanner.example/api/v1/reports/report-abc", readyPayload.Links.Report);
|
||||
Assert.Equal("https://scanner.example/api/v1/policy/revisions/rev-42", readyPayload.Links.Policy);
|
||||
Assert.Equal("https://scanner.example/ui/attestations/report-abc", readyPayload.Links.Attestation);
|
||||
Assert.Equal(envelope.Payload, readyPayload.Dsse?.Payload);
|
||||
Assert.Equal("blocked", readyPayload.Report.Verdict);
|
||||
|
||||
var scanEvent = Assert.Single(publisher.Events, evt => evt.Kind == OrchestratorEventKinds.ScannerScanCompleted);
|
||||
Assert.Equal("tenant-alpha", scanEvent.Tenant);
|
||||
Assert.Equal("scanner.event.scan.completed:tenant-alpha:report-abc", scanEvent.IdempotencyKey);
|
||||
Assert.Equal("sha256:feedface", scanEvent.Scope?.Digest);
|
||||
var scanPayload = Assert.IsType<ScanCompletedEventPayload>(scanEvent.Payload);
|
||||
Assert.Equal("report-abc", scanPayload.ReportId);
|
||||
Assert.Equal("report-abc", scanPayload.ScanId);
|
||||
Assert.Equal("fail", scanPayload.Verdict);
|
||||
var finding = Assert.Single(scanPayload.Findings);
|
||||
Assert.Equal("finding-1", finding.Id);
|
||||
Assert.Equal("runtime", finding.Reachability);
|
||||
Assert.Equal("CVE-2024-9999", finding.Cve);
|
||||
Assert.Equal("https://scanner.example/api/v1/reports/report-abc", scanPayload.Links.Report);
|
||||
Assert.Equal("https://scanner.example/api/v1/policy/revisions/rev-42", scanPayload.Links.Policy);
|
||||
Assert.Equal("https://scanner.example/ui/attestations/report-abc", scanPayload.Links.Attestation);
|
||||
Assert.Equal(envelope.Payload, scanPayload.Dsse?.Payload);
|
||||
Assert.Equal("blocked", scanPayload.Report.Verdict);
|
||||
}
|
||||
|
||||
private sealed class RecordingEventPublisher : IPlatformEventPublisher
|
||||
{
|
||||
public List<OrchestratorEvent> Events { get; } = new();
|
||||
|
||||
public Task PublishAsync(OrchestratorEvent @event, CancellationToken cancellationToken = default)
|
||||
{
|
||||
Events.Add(@event);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user