Files
git.stella-ops.org/src/Scheduler/__Tests/StellaOps.Scheduler.Worker.Tests/SchedulerEventPublisherTests.cs
2025-10-28 15:10:40 +02:00

141 lines
5.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Text.Json.Nodes;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging.Abstractions;
using StellaOps.Notify.Models;
using StellaOps.Notify.Queue;
using StellaOps.Scheduler.Models;
using StellaOps.Scheduler.Queue;
using StellaOps.Scheduler.Worker.Events;
using StellaOps.Scheduler.Worker.Execution;
using Xunit;
namespace StellaOps.Scheduler.Worker.Tests;
public sealed class SchedulerEventPublisherTests
{
[Fact]
public async Task PublishReportReadyAsync_EnqueuesNotifyEvent()
{
var queue = new RecordingNotifyEventQueue();
var options = new NotifyEventQueueOptions();
var publisher = new SchedulerEventPublisher(queue, options, TimeProvider.System, NullLogger<SchedulerEventPublisher>.Instance);
var run = CreateRun();
var message = CreateMessage(run);
var delta = new DeltaSummary(
run.Id,
newFindings: 2,
newCriticals: 1,
newHigh: 1,
newMedium: 0,
newLow: 0);
var result = CreateRunnerImageResult(run.Id, delta);
var impact = new ImpactImage(run.Id, "registry", "repository");
await publisher.PublishReportReadyAsync(run, message, result, impact, CancellationToken.None);
Assert.Single(queue.Messages);
var notifyEvent = queue.Messages[0].Event;
Assert.Equal(NotifyEventKinds.ScannerReportReady, notifyEvent.Kind);
Assert.Equal(run.TenantId, notifyEvent.Tenant);
Assert.NotNull(notifyEvent.Scope);
Assert.Equal("repository", notifyEvent.Scope!.Repo);
var payload = Assert.IsType<JsonObject>(notifyEvent.Payload);
Assert.Equal(result.Report.ReportId, payload["reportId"]!.GetValue<string>());
Assert.Equal("warn", payload["verdict"]!.GetValue<string>());
var deltaNode = Assert.IsType<JsonObject>(payload["delta"]);
Assert.Equal(1, deltaNode["newCritical"]!.GetValue<int>());
}
[Fact]
public async Task PublishRescanDeltaAsync_EnqueuesDeltaEvent()
{
var queue = new RecordingNotifyEventQueue();
var options = new NotifyEventQueueOptions();
var publisher = new SchedulerEventPublisher(queue, options, TimeProvider.System, NullLogger<SchedulerEventPublisher>.Instance);
var run = CreateRun();
var message = CreateMessage(run);
var delta = new DeltaSummary(run.Id, 1, 1, 0, 0, 0);
var impactLookup = new Dictionary<string, ImpactImage>
{
[run.Id] = new ImpactImage(run.Id, "registry", "repository")
};
await publisher.PublishRescanDeltaAsync(run, message, new[] { delta }, impactLookup, CancellationToken.None);
Assert.Single(queue.Messages);
var notifyEvent = queue.Messages[0].Event;
Assert.Equal(NotifyEventKinds.SchedulerRescanDelta, notifyEvent.Kind);
var payload = Assert.IsType<JsonObject>(notifyEvent.Payload);
var digests = Assert.IsType<JsonArray>(payload["impactedDigests"]);
Assert.Equal(run.Id, digests[0]!.GetValue<string>());
}
private const string SampleDigest = "sha256:1111111111111111111111111111111111111111111111111111111111111111";
private static Run CreateRun()
=> new(
id: SampleDigest,
tenantId: "tenant-1",
trigger: RunTrigger.Cron,
state: RunState.Running,
stats: new RunStats(queued: 1, completed: 0),
createdAt: DateTimeOffset.UtcNow,
scheduleId: "schedule-1");
private static RunnerSegmentQueueMessage CreateMessage(Run run)
=> new(
segmentId: $"{run.Id}:0000",
runId: run.Id,
tenantId: run.TenantId,
imageDigests: new[] { run.Id },
scheduleId: run.ScheduleId,
ratePerSecond: null,
usageOnly: true,
attributes: new Dictionary<string, string>(StringComparer.Ordinal)
{
["scheduleMode"] = ScheduleMode.AnalysisOnly.ToString()
});
private static RunnerImageResult CreateRunnerImageResult(string digest, DeltaSummary? delta)
{
var summary = new RunnerReportSummary(
Total: delta?.NewFindings ?? 0,
Blocked: delta?.NewCriticals ?? 0,
Warned: delta?.NewHigh ?? 0,
Ignored: delta?.NewLow ?? 0,
Quieted: 0);
var snapshot = new RunnerReportSnapshot(
ReportId: $"report-{digest[^4..]}",
ImageDigest: digest,
Verdict: "warn",
GeneratedAt: DateTimeOffset.UtcNow,
Summary: summary,
PolicyRevisionId: null,
PolicyDigest: null);
return new RunnerImageResult(digest, delta, ContentRefreshed: false, snapshot, Dsse: null);
}
private sealed class RecordingNotifyEventQueue : INotifyEventQueue
{
public List<NotifyQueueEventMessage> Messages { get; } = new();
public ValueTask<NotifyQueueEnqueueResult> PublishAsync(NotifyQueueEventMessage message, CancellationToken cancellationToken = default)
{
Messages.Add(message);
return ValueTask.FromResult(new NotifyQueueEnqueueResult(Guid.NewGuid().ToString("N"), false));
}
public ValueTask<IReadOnlyList<INotifyQueueLease<NotifyQueueEventMessage>>> LeaseAsync(NotifyQueueLeaseRequest request, CancellationToken cancellationToken = default)
=> throw new NotSupportedException();
public ValueTask<IReadOnlyList<INotifyQueueLease<NotifyQueueEventMessage>>> ClaimExpiredAsync(NotifyQueueClaimOptions options, CancellationToken cancellationToken = default)
=> throw new NotSupportedException();
}
}