Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.ManagementService.Tests/ApiMethodTraceAllCallsTests.cs
T
Joseph Doherty 23157d2bcf feat(inbound-api): ApiMethod.TraceAllCalls (entity, migration, mgmt command, CLI --trace-all-calls)
Flow Debugger (design 2026-09-05 §5.2): bit NOT NULL DEFAULT 0 column + EF migration
20260906034113 with its docs/plans/sql production twin. CreateApiMethodCommand gains
TraceAllCalls=false; UpdateApiMethodCommand gains bool? TraceAllCalls (null = unchanged so an
older CLI never resets it). Management result echoes the flag; CLI create/update expose
--trace-all-calls; README carries the ~7-rows-per-call warning.

Claude-Session: https://claude.ai/code/session_01GSLkkHKz9JrAtmJjmQK3cP
2026-09-05 23:42:52 -04:00

89 lines
3.7 KiB
C#

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;
/// <summary>
/// Flow Debugger design 2026-09-05 §5.2: <c>ApiMethod.TraceAllCalls</c> 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.
/// </summary>
public class ApiMethodTraceAllCallsTests : TestKit, IDisposable
{
private readonly ServiceCollection _services = new();
private readonly IInboundApiRepository _apiRepo = Substitute.For<IInboundApiRepository>();
public ApiMethodTraceAllCallsTests()
{
_services.AddScoped(_ => _apiRepo);
_services.AddScoped(_ => Substitute.For<IAuditService>());
_apiRepo.SaveChangesAsync(Arg.Any<CancellationToken>()).Returns(1);
}
private IActorRef CreateActor() =>
Sys.ActorOf(Props.Create(() => new ManagementActor(
_services.BuildServiceProvider(), NullLogger<ManagementActor>.Instance)));
private static ManagementEnvelope Envelope(object command) =>
new(new AuthenticatedUser("designer", "Designer", new[] { "Designer" }, Array.Empty<string>()),
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<ApiMethod>(m => added = m), Arg.Any<CancellationToken>())
.Returns(Task.CompletedTask);
CreateActor().Tell(Envelope(new CreateApiMethodCommand("Traced", "return 1;", 30, null, null, TraceAllCalls: true)));
var response = ExpectMsg<ManagementSuccess>(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<CancellationToken>()).Returns(existing);
CreateActor().Tell(Envelope(new UpdateApiMethodCommand(7, "return 2;", 10, null, null, TraceAllCalls: false)));
ExpectMsg<ManagementSuccess>(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<CancellationToken>()).Returns(existing);
// The 5-arg shape an older CLI sends: TraceAllCalls == null.
CreateActor().Tell(Envelope(new UpdateApiMethodCommand(7, "return 2;", 10, null, null)));
ExpectMsg<ManagementSuccess>(TimeSpan.FromSeconds(10));
Assert.True(existing.TraceAllCalls);
Assert.Equal("return 2;", existing.Script);
}
}