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(_ => 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(); var runnerQueue = provider.GetRequiredService(); plannerQueue.Should().BeOfType(); runnerQueue.Should().BeOfType(); } [Trait("Category", TestCategories.Unit)] [Fact] public async Task SchedulerQueueHealthCheck_ReturnsHealthy_WhenTransportsReachable() { var healthCheck = new SchedulerQueueHealthCheck( new FakePlannerQueue(failPing: false), new FakeRunnerQueue(failPing: false), NullLogger.Instance); var context = new HealthCheckContext { Registration = new HealthCheckRegistration("scheduler-queue", healthCheck, HealthStatus.Unhealthy, Array.Empty()) }; 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.Instance); var context = new HealthCheckContext { Registration = new HealthCheckRegistration("scheduler-queue", healthCheck, HealthStatus.Unhealthy, Array.Empty()) }; var result = await healthCheck.CheckHealthAsync(context); result.Status.Should().Be(HealthStatus.Unhealthy); result.Description.Should().Contain("runner transport unreachable"); } private abstract class FakeQueue : ISchedulerQueue, ISchedulerQueueTransportDiagnostics { private readonly bool _failPing; protected FakeQueue(bool failPing) { _failPing = failPing; } public ValueTask EnqueueAsync(TMessage message, CancellationToken cancellationToken = default) => ValueTask.FromResult(new SchedulerQueueEnqueueResult("stub", false)); public ValueTask>> LeaseAsync(SchedulerQueueLeaseRequest request, CancellationToken cancellationToken = default) => ValueTask.FromResult>>(Array.Empty>()); public ValueTask>> ClaimExpiredAsync(SchedulerQueueClaimOptions options, CancellationToken cancellationToken = default) => ValueTask.FromResult>>(Array.Empty>()); public ValueTask PingAsync(CancellationToken cancellationToken) => _failPing ? ValueTask.FromException(new InvalidOperationException("ping failed")) : ValueTask.CompletedTask; } private sealed class FakePlannerQueue : FakeQueue, ISchedulerPlannerQueue { public FakePlannerQueue(bool failPing) : base(failPing) { } } private sealed class FakeRunnerQueue : FakeQueue, ISchedulerRunnerQueue { public FakeRunnerQueue(bool failPing) : base(failPing) { } } }