Files
git.stella-ops.org/src/JobEngine/StellaOps.Scheduler.__Tests/StellaOps.Scheduler.Queue.Tests/SchedulerQueueServiceCollectionExtensionsTests.cs

121 lines
4.7 KiB
C#

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;
using StellaOps.TestKit;
namespace StellaOps.Scheduler.Queue.Tests;
public sealed class SchedulerQueueServiceCollectionExtensionsTests
{
[Trait("Category", TestCategories.Unit)]
[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>();
}
[Trait("Category", TestCategories.Unit)]
[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);
}
[Trait("Category", TestCategories.Unit)]
[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)
{
}
}
}