Merge branch 'worktree-agent-aa92b89ae2edc1ead' into fix/flow-debugger-arch-review
This commit is contained in:
@@ -4,10 +4,13 @@ using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.AuditLog.Configuration;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Entities.InboundApi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.InboundAPI.Tests;
|
||||
|
||||
@@ -57,6 +60,77 @@ public class InboundScriptExecutorTests
|
||||
Assert.False(captured[1].Trace);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Executor_MethodWithSkipBodyCapture_RoutesWithBodyCaptureOff()
|
||||
{
|
||||
// Arch-review F11: a method configured with PerTargetOverrides[method].SkipBodyCapture
|
||||
// must build its Route helper WithBodyCapture(false), so the always-on RouteCall row
|
||||
// carries no request/response summary — mirroring AuditWriteMiddleware's own
|
||||
// SkipBodyCapture lookup for the inbound row (AuditWriteMiddleware.cs:183-187).
|
||||
var locator = Substitute.For<IInstanceLocator>();
|
||||
var router = Substitute.For<IInstanceRouter>();
|
||||
locator.GetSiteIdForInstanceAsync("inst", Arg.Any<CancellationToken>()).Returns("SiteA");
|
||||
router.RouteToCallAsync("SiteA", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
|
||||
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 1, null, DateTimeOffset.UtcNow));
|
||||
|
||||
var writer = Substitute.For<ICentralAuditWriter>();
|
||||
AuditEvent? written = null;
|
||||
writer.WriteAsync(Arg.Do<AuditEvent>(e => written = e), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
var route = new RouteHelper(locator, router, writer, null);
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.Configure<AuditLogOptions>(o =>
|
||||
o.PerTargetOverrides["routed"] = new PerTargetRedactionOverride { SkipBodyCapture = true });
|
||||
using var provider = services.BuildServiceProvider();
|
||||
|
||||
var executor = new InboundScriptExecutor(NullLogger<InboundScriptExecutor>.Instance, provider);
|
||||
var method = new ApiMethod("routed", "x") { Id = 9, TimeoutSeconds = 10 };
|
||||
executor.RegisterHandler("routed", ctx => ctx.Route.To("inst").Call("work", new { x = 1 }));
|
||||
|
||||
var result = await executor.ExecuteAsync(method, new Dictionary<string, object?>(), route, TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.True(result.Success, result.ErrorMessage);
|
||||
Assert.NotNull(written);
|
||||
var row = written!.AsRow();
|
||||
Assert.Null(row.RequestSummary);
|
||||
Assert.Null(row.ResponseSummary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Executor_MethodWithoutSkipBodyCapture_RoutesWithBodyCaptureOn()
|
||||
{
|
||||
// Control: no PerTargetOverrides entry for this method → default behaviour unchanged.
|
||||
var locator = Substitute.For<IInstanceLocator>();
|
||||
var router = Substitute.For<IInstanceRouter>();
|
||||
locator.GetSiteIdForInstanceAsync("inst", Arg.Any<CancellationToken>()).Returns("SiteA");
|
||||
router.RouteToCallAsync("SiteA", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
|
||||
.Returns(ci => new RouteToCallResponse(((RouteToCallRequest)ci[1]).CorrelationId, true, 1, null, DateTimeOffset.UtcNow));
|
||||
|
||||
var writer = Substitute.For<ICentralAuditWriter>();
|
||||
AuditEvent? written = null;
|
||||
writer.WriteAsync(Arg.Do<AuditEvent>(e => written = e), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
var route = new RouteHelper(locator, router, writer, null);
|
||||
|
||||
var services = new ServiceCollection();
|
||||
services.Configure<AuditLogOptions>(o =>
|
||||
o.PerTargetOverrides["other-method"] = new PerTargetRedactionOverride { SkipBodyCapture = true });
|
||||
using var provider = services.BuildServiceProvider();
|
||||
|
||||
var executor = new InboundScriptExecutor(NullLogger<InboundScriptExecutor>.Instance, provider);
|
||||
var method = new ApiMethod("routed", "x") { Id = 9, TimeoutSeconds = 10 };
|
||||
executor.RegisterHandler("routed", ctx => ctx.Route.To("inst").Call("work", new { x = 1 }));
|
||||
|
||||
var result = await executor.ExecuteAsync(method, new Dictionary<string, object?>(), route, TimeSpan.FromSeconds(10));
|
||||
|
||||
Assert.True(result.Success, result.ErrorMessage);
|
||||
Assert.NotNull(written);
|
||||
var row = written!.AsRow();
|
||||
Assert.Contains("\"x\":1", row.RequestSummary);
|
||||
Assert.Equal("1", row.ResponseSummary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RegisteredHandler_ExecutesSuccessfully()
|
||||
{
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using NSubstitute;
|
||||
using ZB.MOM.WW.Audit;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Interfaces.Services;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types.Audit;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.InboundAPI.Tests;
|
||||
|
||||
@@ -703,4 +705,81 @@ public class RouteHelperTests
|
||||
await Assert.ThrowsAsync<RouteSiteScopeException>(
|
||||
() => helper.To("inst-1").Call("doWork"));
|
||||
}
|
||||
|
||||
// --- Arch-review F11: WithBodyCapture ---
|
||||
|
||||
[Fact]
|
||||
public async Task RouteCallRow_WithBodyCaptureOff_HasNoRequestOrResponseSummary()
|
||||
{
|
||||
SiteResolves("inst-1", "SiteA");
|
||||
_router.RouteToCallAsync("SiteA", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
|
||||
.Returns(ci => new RouteToCallResponse(
|
||||
((RouteToCallRequest)ci[1]).CorrelationId, true, 99, null, DateTimeOffset.UtcNow));
|
||||
var writer = Substitute.For<ICentralAuditWriter>();
|
||||
AuditEvent? written = null;
|
||||
writer.WriteAsync(Arg.Do<AuditEvent>(e => written = e), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
var helper = new RouteHelper(_locator, _router, writer, null).WithBodyCapture(false);
|
||||
|
||||
var result = await helper.To("inst-1").Call("doWork", new { x = 1 });
|
||||
|
||||
Assert.Equal(99, result);
|
||||
Assert.NotNull(written);
|
||||
var row = written!.AsRow();
|
||||
Assert.Null(row.RequestSummary);
|
||||
Assert.Null(row.ResponseSummary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteCallRow_WithBodyCaptureOn_ByDefault_HasRequestAndResponseSummary()
|
||||
{
|
||||
SiteResolves("inst-1", "SiteA");
|
||||
_router.RouteToCallAsync("SiteA", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
|
||||
.Returns(ci => new RouteToCallResponse(
|
||||
((RouteToCallRequest)ci[1]).CorrelationId, true, 99, null, DateTimeOffset.UtcNow));
|
||||
var writer = Substitute.For<ICentralAuditWriter>();
|
||||
AuditEvent? written = null;
|
||||
writer.WriteAsync(Arg.Do<AuditEvent>(e => written = e), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
var helper = new RouteHelper(_locator, _router, writer, null);
|
||||
|
||||
await helper.To("inst-1").Call("doWork", new { x = 1 });
|
||||
|
||||
Assert.NotNull(written);
|
||||
var row = written!.AsRow();
|
||||
Assert.Contains("\"x\":1", row.RequestSummary);
|
||||
Assert.Equal("99", row.ResponseSummary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WithBodyCapture_SurvivesTheOtherWithCopies()
|
||||
{
|
||||
// Mirrors WithSiteScope_SurvivesTheOtherWithCopies (Task 3): the flag must not be
|
||||
// dropped by the copies InboundScriptExecutor makes (WithDeadline /
|
||||
// WithRequestAborted / WithParentExecutionId / WithTrace / WithSiteScope).
|
||||
SiteResolves("inst-1", "SiteA");
|
||||
_router.RouteToCallAsync("SiteA", Arg.Any<RouteToCallRequest>(), Arg.Any<CancellationToken>())
|
||||
.Returns(ci => new RouteToCallResponse(
|
||||
((RouteToCallRequest)ci[1]).CorrelationId, true, 99, null, DateTimeOffset.UtcNow));
|
||||
var writer = Substitute.For<ICentralAuditWriter>();
|
||||
AuditEvent? written = null;
|
||||
writer.WriteAsync(Arg.Do<AuditEvent>(e => written = e), Arg.Any<CancellationToken>())
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var helper = new RouteHelper(_locator, _router, writer, null)
|
||||
.WithBodyCapture(false)
|
||||
.WithDeadline(CancellationToken.None)
|
||||
.WithRequestAborted(CancellationToken.None)
|
||||
.WithParentExecutionId(Guid.NewGuid())
|
||||
.WithTrace(true)
|
||||
.WithTraceSink(null)
|
||||
.WithSiteScope(Scope("SiteA"));
|
||||
|
||||
await helper.To("inst-1").Call("doWork", new { x = 1 });
|
||||
|
||||
Assert.NotNull(written);
|
||||
var row = written!.AsRow();
|
||||
Assert.Null(row.RequestSummary);
|
||||
Assert.Null(row.ResponseSummary);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user