141 lines
5.4 KiB
C#
141 lines
5.4 KiB
C#
using System.Net;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using Microsoft.Extensions.Logging;
|
|
using Microsoft.Extensions.Options;
|
|
using StellaOps.Scheduler.Models;
|
|
using StellaOps.Scheduler.WebService.GraphJobs;
|
|
using StellaOps.Scheduler.WebService.Options;
|
|
|
|
namespace StellaOps.Scheduler.WebService.Tests;
|
|
|
|
public sealed class CartographerWebhookClientTests
|
|
{
|
|
[Fact]
|
|
public async Task NotifyAsync_PostsPayload_WhenEnabled()
|
|
{
|
|
var handler = new RecordingHandler();
|
|
var httpClient = new HttpClient(handler);
|
|
var options = Microsoft.Extensions.Options.Options.Create(new SchedulerCartographerOptions
|
|
{
|
|
Webhook =
|
|
{
|
|
Enabled = true,
|
|
Endpoint = "https://cartographer.local/hooks/graph-completed",
|
|
ApiKeyHeader = "X-Api-Key",
|
|
ApiKey = "secret"
|
|
}
|
|
});
|
|
using var loggerFactory = LoggerFactory.Create(builder => builder.AddDebug());
|
|
var client = new CartographerWebhookClient(httpClient, new OptionsMonitorStub<SchedulerCartographerOptions>(options), loggerFactory.CreateLogger<CartographerWebhookClient>());
|
|
|
|
var job = new GraphBuildJob(
|
|
id: "gbj_test",
|
|
tenantId: "tenant-alpha",
|
|
sbomId: "sbom",
|
|
sbomVersionId: "sbom_v1",
|
|
sbomDigest: "sha256:" + new string('a', 64),
|
|
status: GraphJobStatus.Completed,
|
|
trigger: GraphBuildJobTrigger.Backfill,
|
|
createdAt: DateTimeOffset.UtcNow,
|
|
graphSnapshotId: "snap",
|
|
attempts: 1,
|
|
cartographerJobId: "carto-123",
|
|
correlationId: "corr-1",
|
|
startedAt: null,
|
|
completedAt: DateTimeOffset.UtcNow,
|
|
error: null,
|
|
metadata: Array.Empty<KeyValuePair<string, string>>());
|
|
|
|
var notification = new GraphJobCompletionNotification(
|
|
job.TenantId,
|
|
GraphJobQueryType.Build,
|
|
GraphJobStatus.Completed,
|
|
DateTimeOffset.UtcNow,
|
|
GraphJobResponse.From(job),
|
|
"oras://snap/result",
|
|
"corr-1",
|
|
null);
|
|
|
|
await client.NotifyAsync(notification, CancellationToken.None);
|
|
|
|
Assert.NotNull(handler.LastRequest);
|
|
Assert.Equal("https://cartographer.local/hooks/graph-completed", handler.LastRequest.RequestUri!.ToString());
|
|
Assert.True(handler.LastRequest.Headers.TryGetValues("X-Api-Key", out var values) && values!.Single() == "secret");
|
|
var json = JsonSerializer.Deserialize<JsonElement>(handler.LastPayload!);
|
|
Assert.Equal("gbj_test", json.GetProperty("jobId").GetString());
|
|
Assert.Equal("tenant-alpha", json.GetProperty("tenantId").GetString());
|
|
}
|
|
|
|
[Fact]
|
|
public async Task NotifyAsync_Skips_WhenDisabled()
|
|
{
|
|
var handler = new RecordingHandler();
|
|
var httpClient = new HttpClient(handler);
|
|
var options = Microsoft.Extensions.Options.Options.Create(new SchedulerCartographerOptions());
|
|
using var loggerFactory = LoggerFactory.Create(builder => builder.AddDebug());
|
|
var client = new CartographerWebhookClient(httpClient, new OptionsMonitorStub<SchedulerCartographerOptions>(options), loggerFactory.CreateLogger<CartographerWebhookClient>());
|
|
|
|
var job = new GraphOverlayJob(
|
|
id: "goj-test",
|
|
tenantId: "tenant-alpha",
|
|
graphSnapshotId: "snap",
|
|
overlayKind: GraphOverlayKind.Policy,
|
|
overlayKey: "policy@1",
|
|
status: GraphJobStatus.Completed,
|
|
trigger: GraphOverlayJobTrigger.Manual,
|
|
createdAt: DateTimeOffset.UtcNow,
|
|
subjects: Array.Empty<string>(),
|
|
attempts: 1,
|
|
correlationId: null,
|
|
startedAt: null,
|
|
completedAt: DateTimeOffset.UtcNow,
|
|
error: null,
|
|
metadata: Array.Empty<KeyValuePair<string, string>>());
|
|
|
|
var notification = new GraphJobCompletionNotification(
|
|
job.TenantId,
|
|
GraphJobQueryType.Overlay,
|
|
GraphJobStatus.Completed,
|
|
DateTimeOffset.UtcNow,
|
|
GraphJobResponse.From(job),
|
|
null,
|
|
null,
|
|
null);
|
|
|
|
await client.NotifyAsync(notification, CancellationToken.None);
|
|
|
|
Assert.Null(handler.LastRequest);
|
|
}
|
|
|
|
private sealed class RecordingHandler : HttpMessageHandler
|
|
{
|
|
public HttpRequestMessage? LastRequest { get; private set; }
|
|
public string? LastPayload { get; private set; }
|
|
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
LastRequest = request;
|
|
LastPayload = request.Content is null ? null : request.Content.ReadAsStringAsync(cancellationToken).Result;
|
|
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
|
|
}
|
|
}
|
|
|
|
private sealed class OptionsMonitorStub<T> : IOptionsMonitor<T> where T : class
|
|
{
|
|
private readonly IOptions<T> _options;
|
|
|
|
public OptionsMonitorStub(IOptions<T> options)
|
|
{
|
|
_options = options;
|
|
}
|
|
|
|
public T CurrentValue => _options.Value;
|
|
|
|
public T Get(string? name) => _options.Value;
|
|
|
|
public IDisposable? OnChange(Action<T, string?> listener) => null;
|
|
}
|
|
}
|