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
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user