Restructure solution layout by module
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user