using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; namespace ZB.MOM.WW.ScadaBridge.InboundAPI.Tests; /// Tests for (review 2026-10-21 05/U3). public class InboundMethodWarmupServiceTests { private sealed class Lifetime : IHostApplicationLifetime { public CancellationTokenSource StartedCts { get; } = new(); public CancellationToken ApplicationStarted => StartedCts.Token; public CancellationToken ApplicationStopping => CancellationToken.None; public CancellationToken ApplicationStopped => CancellationToken.None; public void StopApplication() { } } private sealed class Gate(bool active) : IActiveNodeGate { public bool IsActiveNode => active; } private static (InboundMethodWarmupService svc, InboundScriptExecutor exec, Lifetime life) Build(bool active) { var repo = Substitute.For(); repo.GetAllApiMethodsAsync(Arg.Any()) .Returns(new List { new("warm", "return 1;") { Id = 1, TimeoutSeconds = 10 } }); var provider = new ServiceCollection().AddSingleton(repo).BuildServiceProvider(); var exec = new InboundScriptExecutor(NullLogger.Instance, Substitute.For()); var life = new Lifetime(); var svc = new InboundMethodWarmupService( provider.GetRequiredService(), exec, life, NullLogger.Instance, new Gate(active)); return (svc, exec, life); } /// Verifies the warm-up waits for ApplicationStarted and then compiles on an active node. /// A task that represents the asynchronous operation. [Fact] public async Task ActiveNode_CompilesAfterApplicationStarted_NotBefore() { var (svc, exec, life) = Build(active: true); await svc.StartAsync(CancellationToken.None); await Task.Delay(200); Assert.False(exec.HasHandler("warm")); life.StartedCts.Cancel(); for (var i = 0; i < 100 && !exec.HasHandler("warm"); i++) await Task.Delay(50); Assert.True(exec.HasHandler("warm")); await svc.StopAsync(CancellationToken.None); } /// Verifies a standby node does not warm up. /// A task that represents the asynchronous operation. [Fact] public async Task StandbyNode_DoesNotCompile() { var (svc, exec, life) = Build(active: false); await svc.StartAsync(CancellationToken.None); life.StartedCts.Cancel(); await Task.Delay(500); Assert.False(exec.HasHandler("warm")); await svc.StopAsync(CancellationToken.None); } }