Add tests and implement StubBearer authentication for Signer endpoints
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Created SignerEndpointsTests to validate the SignDsse and VerifyReferrers endpoints. - Implemented StubBearerAuthenticationDefaults and StubBearerAuthenticationHandler for token-based authentication. - Developed ConcelierExporterClient for managing Trivy DB settings and export operations. - Added TrivyDbSettingsPageComponent for UI interactions with Trivy DB settings, including form handling and export triggering. - Implemented styles and HTML structure for Trivy DB settings page. - Created NotifySmokeCheck tool for validating Redis event streams and Notify deliveries.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using DotNet.Testcontainers.Builders;
|
||||
using DotNet.Testcontainers.Containers;
|
||||
@@ -9,7 +10,7 @@ using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StackExchange.Redis;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Queue.Redis;
|
||||
using Xunit;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scheduler.Queue.Tests;
|
||||
|
||||
@@ -52,7 +53,10 @@ public sealed class RedisSchedulerQueueTests : IAsyncLifetime
|
||||
[Fact]
|
||||
public async Task PlannerQueue_EnqueueLeaseAck_RemovesMessage()
|
||||
{
|
||||
SkipIfUnavailable();
|
||||
if (SkipIfUnavailable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = CreateOptions();
|
||||
|
||||
@@ -83,9 +87,12 @@ public sealed class RedisSchedulerQueueTests : IAsyncLifetime
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunnerQueue_Retry_IncrementsDeliveryAttempt()
|
||||
public async Task RunnerQueue_Retry_IncrementsDeliveryAttempt()
|
||||
{
|
||||
SkipIfUnavailable();
|
||||
if (SkipIfUnavailable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = CreateOptions();
|
||||
options.RetryInitialBackoff = TimeSpan.Zero;
|
||||
@@ -118,7 +125,10 @@ public sealed class RedisSchedulerQueueTests : IAsyncLifetime
|
||||
[Fact]
|
||||
public async Task PlannerQueue_ClaimExpired_ReassignsLease()
|
||||
{
|
||||
SkipIfUnavailable();
|
||||
if (SkipIfUnavailable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = CreateOptions();
|
||||
|
||||
@@ -142,8 +152,74 @@ public sealed class RedisSchedulerQueueTests : IAsyncLifetime
|
||||
reclaimed[0].Consumer.Should().Be("planner-b");
|
||||
reclaimed[0].RunId.Should().Be(message.Run.Id);
|
||||
|
||||
await reclaimed[0].AcknowledgeAsync();
|
||||
}
|
||||
await reclaimed[0].AcknowledgeAsync();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PlannerQueue_RecordsDepthMetrics()
|
||||
{
|
||||
if (SkipIfUnavailable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = CreateOptions();
|
||||
|
||||
await using var queue = new RedisSchedulerPlannerQueue(
|
||||
options,
|
||||
options.Redis,
|
||||
NullLogger<RedisSchedulerPlannerQueue>.Instance,
|
||||
TimeProvider.System,
|
||||
async config => (IConnectionMultiplexer)await ConnectionMultiplexer.ConnectAsync(config).ConfigureAwait(false));
|
||||
|
||||
var message = TestData.CreatePlannerMessage();
|
||||
|
||||
await queue.EnqueueAsync(message);
|
||||
|
||||
var depths = SchedulerQueueMetrics.SnapshotDepths();
|
||||
depths.TryGetValue(("redis", "planner"), out var plannerDepth)
|
||||
.Should().BeTrue();
|
||||
plannerDepth.Should().Be(1);
|
||||
|
||||
var leases = await queue.LeaseAsync(new SchedulerQueueLeaseRequest("planner-depth", 1, options.DefaultLeaseDuration));
|
||||
leases.Should().ContainSingle();
|
||||
await leases[0].AcknowledgeAsync();
|
||||
|
||||
depths = SchedulerQueueMetrics.SnapshotDepths();
|
||||
depths.TryGetValue(("redis", "planner"), out plannerDepth).Should().BeTrue();
|
||||
plannerDepth.Should().Be(0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RunnerQueue_DropWhenDeadLetterDisabled()
|
||||
{
|
||||
if (SkipIfUnavailable())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var options = CreateOptions();
|
||||
options.MaxDeliveryAttempts = 1;
|
||||
options.DeadLetterEnabled = false;
|
||||
|
||||
await using var queue = new RedisSchedulerRunnerQueue(
|
||||
options,
|
||||
options.Redis,
|
||||
NullLogger<RedisSchedulerRunnerQueue>.Instance,
|
||||
TimeProvider.System,
|
||||
async config => (IConnectionMultiplexer)await ConnectionMultiplexer.ConnectAsync(config).ConfigureAwait(false));
|
||||
|
||||
var message = TestData.CreateRunnerMessage();
|
||||
await queue.EnqueueAsync(message);
|
||||
|
||||
var lease = (await queue.LeaseAsync(new SchedulerQueueLeaseRequest("runner-drop", 1, options.DefaultLeaseDuration)))[0];
|
||||
|
||||
await lease.ReleaseAsync(SchedulerQueueReleaseDisposition.Retry);
|
||||
|
||||
var depths = SchedulerQueueMetrics.SnapshotDepths();
|
||||
depths.TryGetValue(("redis", "runner"), out var runnerDepth).Should().BeTrue();
|
||||
runnerDepth.Should().Be(0);
|
||||
}
|
||||
|
||||
private SchedulerQueueOptions CreateOptions()
|
||||
{
|
||||
@@ -181,13 +257,14 @@ public sealed class RedisSchedulerQueueTests : IAsyncLifetime
|
||||
};
|
||||
}
|
||||
|
||||
private void SkipIfUnavailable()
|
||||
{
|
||||
if (_skipReason is not null)
|
||||
{
|
||||
Skip.If(true, _skipReason);
|
||||
}
|
||||
}
|
||||
private bool SkipIfUnavailable()
|
||||
{
|
||||
if (_skipReason is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool IsDockerUnavailable(Exception exception)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using FluentAssertions;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Diagnostics.HealthChecks;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using StellaOps.Scheduler.Models;
|
||||
using StellaOps.Scheduler.Queue;
|
||||
using StellaOps.Scheduler.Queue.Nats;
|
||||
using Xunit;
|
||||
|
||||
namespace StellaOps.Scheduler.Queue.Tests;
|
||||
|
||||
public sealed class SchedulerQueueServiceCollectionExtensionsTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task AddSchedulerQueues_RegistersNatsTransport()
|
||||
{
|
||||
var services = new ServiceCollection();
|
||||
services.AddSingleton<ILoggerFactory>(_ => NullLoggerFactory.Instance);
|
||||
services.AddSchedulerQueues(new ConfigurationBuilder().Build());
|
||||
|
||||
var optionsDescriptor = services.First(descriptor => descriptor.ServiceType == typeof(SchedulerQueueOptions));
|
||||
var options = (SchedulerQueueOptions)optionsDescriptor.ImplementationInstance!;
|
||||
options.Kind = SchedulerQueueTransportKind.Nats;
|
||||
options.Nats.Url = "nats://localhost:4222";
|
||||
|
||||
await using var provider = services.BuildServiceProvider();
|
||||
|
||||
var plannerQueue = provider.GetRequiredService<ISchedulerPlannerQueue>();
|
||||
var runnerQueue = provider.GetRequiredService<ISchedulerRunnerQueue>();
|
||||
|
||||
plannerQueue.Should().BeOfType<NatsSchedulerPlannerQueue>();
|
||||
runnerQueue.Should().BeOfType<NatsSchedulerRunnerQueue>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchedulerQueueHealthCheck_ReturnsHealthy_WhenTransportsReachable()
|
||||
{
|
||||
var healthCheck = new SchedulerQueueHealthCheck(
|
||||
new FakePlannerQueue(failPing: false),
|
||||
new FakeRunnerQueue(failPing: false),
|
||||
NullLogger<SchedulerQueueHealthCheck>.Instance);
|
||||
|
||||
var context = new HealthCheckContext
|
||||
{
|
||||
Registration = new HealthCheckRegistration("scheduler-queue", healthCheck, HealthStatus.Unhealthy, Array.Empty<string>())
|
||||
};
|
||||
|
||||
var result = await healthCheck.CheckHealthAsync(context);
|
||||
|
||||
result.Status.Should().Be(HealthStatus.Healthy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SchedulerQueueHealthCheck_ReturnsUnhealthy_WhenRunnerPingFails()
|
||||
{
|
||||
var healthCheck = new SchedulerQueueHealthCheck(
|
||||
new FakePlannerQueue(failPing: false),
|
||||
new FakeRunnerQueue(failPing: true),
|
||||
NullLogger<SchedulerQueueHealthCheck>.Instance);
|
||||
|
||||
var context = new HealthCheckContext
|
||||
{
|
||||
Registration = new HealthCheckRegistration("scheduler-queue", healthCheck, HealthStatus.Unhealthy, Array.Empty<string>())
|
||||
};
|
||||
|
||||
var result = await healthCheck.CheckHealthAsync(context);
|
||||
|
||||
result.Status.Should().Be(HealthStatus.Unhealthy);
|
||||
result.Description.Should().Contain("runner transport unreachable");
|
||||
}
|
||||
private abstract class FakeQueue<TMessage> : ISchedulerQueue<TMessage>, ISchedulerQueueTransportDiagnostics
|
||||
{
|
||||
private readonly bool _failPing;
|
||||
|
||||
protected FakeQueue(bool failPing)
|
||||
{
|
||||
_failPing = failPing;
|
||||
}
|
||||
|
||||
public ValueTask<SchedulerQueueEnqueueResult> EnqueueAsync(TMessage message, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult(new SchedulerQueueEnqueueResult("stub", false));
|
||||
|
||||
public ValueTask<IReadOnlyList<ISchedulerQueueLease<TMessage>>> LeaseAsync(SchedulerQueueLeaseRequest request, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult<IReadOnlyList<ISchedulerQueueLease<TMessage>>>(Array.Empty<ISchedulerQueueLease<TMessage>>());
|
||||
|
||||
public ValueTask<IReadOnlyList<ISchedulerQueueLease<TMessage>>> ClaimExpiredAsync(SchedulerQueueClaimOptions options, CancellationToken cancellationToken = default)
|
||||
=> ValueTask.FromResult<IReadOnlyList<ISchedulerQueueLease<TMessage>>>(Array.Empty<ISchedulerQueueLease<TMessage>>());
|
||||
|
||||
public ValueTask PingAsync(CancellationToken cancellationToken)
|
||||
=> _failPing
|
||||
? ValueTask.FromException(new InvalidOperationException("ping failed"))
|
||||
: ValueTask.CompletedTask;
|
||||
}
|
||||
|
||||
private sealed class FakePlannerQueue : FakeQueue<PlannerQueueMessage>, ISchedulerPlannerQueue
|
||||
{
|
||||
public FakePlannerQueue(bool failPing) : base(failPing)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FakeRunnerQueue : FakeQueue<RunnerSegmentQueueMessage>, ISchedulerRunnerQueue
|
||||
{
|
||||
public FakeRunnerQueue(bool failPing) : base(failPing)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,12 @@
|
||||
<UseConcelierTestInfra>false</UseConcelierTestInfra>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="DotNet.Testcontainers" Version="1.7.0-beta.2269" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="DotNet.Testcontainers" Version="1.7.0-beta.2269" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="8.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="8.0.0" />
|
||||
<PackageReference Include="xunit" Version="2.9.2" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
||||
Reference in New Issue
Block a user