using Akka.Actor; using Akka.TestKit.Xunit2; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging.Abstractions; using NSubstitute; using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Repositories; using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; namespace ZB.MOM.WW.ScadaBridge.ManagementService.Tests; /// /// Flow Debugger design 2026-09-05 ยง5.2: ApiMethod.TraceAllCalls round-trips through the /// Create/Update management commands, defaults to off, and an Update that omits it (an older /// CLI) leaves the stored value unchanged. /// public class ApiMethodTraceAllCallsTests : TestKit, IDisposable { private readonly ServiceCollection _services = new(); private readonly IInboundApiRepository _apiRepo = Substitute.For(); public ApiMethodTraceAllCallsTests() { _services.AddScoped(_ => _apiRepo); _services.AddScoped(_ => Substitute.For()); _apiRepo.SaveChangesAsync(Arg.Any()).Returns(1); } private IActorRef CreateActor() => Sys.ActorOf(Props.Create(() => new ManagementActor( _services.BuildServiceProvider(), NullLogger.Instance))); private static ManagementEnvelope Envelope(object command) => new(new AuthenticatedUser("designer", "Designer", new[] { "Designer" }, Array.Empty()), command, Guid.NewGuid().ToString("N")); void IDisposable.Dispose() => Shutdown(); [Fact] public void Commands_DefaultToUntraced_ForTheFiveArgConstructor() { Assert.False(new CreateApiMethodCommand("m", "return 1;", 30, null, null).TraceAllCalls); Assert.Null(new UpdateApiMethodCommand(1, "return 1;", 30, null, null).TraceAllCalls); Assert.False(new ApiMethod("m", "return 1;").TraceAllCalls); } [Fact] public void Create_WithTraceAllCalls_PersistsTheFlag_AndEchoesItInTheResult() { ApiMethod? added = null; _apiRepo.AddApiMethodAsync(Arg.Do(m => added = m), Arg.Any()) .Returns(Task.CompletedTask); CreateActor().Tell(Envelope(new CreateApiMethodCommand("Traced", "return 1;", 30, null, null, TraceAllCalls: true))); var response = ExpectMsg(TimeSpan.FromSeconds(10)); Assert.NotNull(added); Assert.True(added!.TraceAllCalls); Assert.Contains("\"traceAllCalls\":true", response.JsonData.Replace(" ", "")); } [Fact] public void Update_WithExplicitFalse_ClearsTheFlag() { var existing = new ApiMethod("M", "return 1;") { Id = 7, TimeoutSeconds = 10, TraceAllCalls = true }; _apiRepo.GetApiMethodByIdAsync(7, Arg.Any()).Returns(existing); CreateActor().Tell(Envelope(new UpdateApiMethodCommand(7, "return 2;", 10, null, null, TraceAllCalls: false))); ExpectMsg(TimeSpan.FromSeconds(10)); Assert.False(existing.TraceAllCalls); } [Fact] public void Update_OmittingTheFlag_LeavesTheStoredValueUnchanged() { var existing = new ApiMethod("M", "return 1;") { Id = 7, TimeoutSeconds = 10, TraceAllCalls = true }; _apiRepo.GetApiMethodByIdAsync(7, Arg.Any()).Returns(existing); // The 5-arg shape an older CLI sends: TraceAllCalls == null. CreateActor().Tell(Envelope(new UpdateApiMethodCommand(7, "return 2;", 10, null, null))); ExpectMsg(TimeSpan.FromSeconds(10)); Assert.True(existing.TraceAllCalls); Assert.Equal("return 2;", existing.Script); } }