using Bunit;
using Bunit.TestDoubles;
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.DependencyInjection;
using ZB.MOM.WW.ScadaBridge.CentralUI.Components.Audit;
using ZB.MOM.WW.ScadaBridge.CentralUI.Services;
using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums;
namespace ZB.MOM.WW.ScadaBridge.CentralUI.Tests.Audit;
///
/// Flow Debugger drill-ins (design 2026-09-05 §4, plan Task 21): the audit drawer's
/// detail body and the Execution Chain page both offer Open in Flow Debugger,
/// and the inbound-method form carries the TraceAllCalls checkbox wired to both
/// save paths.
///
public class FlowDebuggerDrillinTests : BunitContext
{
public FlowDebuggerDrillinTests()
{
JSInterop.Mode = JSRuntimeMode.Loose;
}
private static AuditEventView MakeEvent(Guid? executionId) => new()
{
EventId = Guid.Parse("11111111-2222-3333-4444-555555555555"),
OccurredAtUtc = new DateTime(2026, 9, 5, 12, 30, 45, DateTimeKind.Utc),
IngestedAtUtc = new DateTime(2026, 9, 5, 12, 30, 46, DateTimeKind.Utc),
Channel = AuditChannel.ApiInbound,
Kind = AuditKind.InboundRequest,
ExecutionId = executionId,
Actor = "tester",
Target = "POST /api/DelmiaRecipeDownload",
Status = AuditStatus.Delivered,
DurationMs = 42,
};
private static string SrcRoot
{
get
{
var dir = AppContext.BaseDirectory;
for (var i = 0; i < 6 && dir is not null; i++)
dir = Directory.GetParent(dir)?.FullName;
return Path.Combine(dir!, "src", "ZB.MOM.WW.ScadaBridge.CentralUI", "Components");
}
}
private static string Read(params string[] relative)
=> File.ReadAllText(Path.Combine([SrcRoot, .. relative]));
[Fact]
public void AuditEventDetail_WithExecutionId_OffersOpenInFlowDebugger_AndNavigatesToReplay()
{
var exec = Guid.Parse("12345678-aaaa-bbbb-cccc-1234567890ab");
var cut = Render(p => p.Add(c => c.Event, MakeEvent(exec)));
cut.Find("[data-test=\"open-flow-debugger\"]").Click();
var nav = (BunitNavigationManager)Services.GetRequiredService();
Assert.Contains($"/deployment/flow-debugger?executionId={exec}", nav.Uri);
}
[Fact]
public void AuditEventDetail_WithoutExecutionId_HidesTheButton()
{
var cut = Render(p => p.Add(c => c.Event, MakeEvent(null)));
Assert.DoesNotContain("data-test=\"open-flow-debugger\"", cut.Markup);
}
[Fact]
public void ExecutionTreePage_LinksToTheFlowDebuggerReplay_BesideTheAuditLogLink()
{
var markup = Read("Pages", "Audit", "ExecutionTreePage.razor");
Assert.Contains("data-test=\"open-flow-debugger\"", markup);
Assert.Contains("/deployment/flow-debugger?executionId={_executionId}", markup);
// Same block as the existing back-to-log link, so both actions sit together.
Assert.True(markup.IndexOf("execution-tree-back-to-log", StringComparison.Ordinal)
< markup.IndexOf("open-flow-debugger", StringComparison.Ordinal));
}
[Fact]
public void ApiMethodForm_HasTraceAllCallsCheckbox_WiredToBothSavePaths()
{
var markup = Read("Pages", "Design", "ApiMethodForm.razor");
Assert.Contains("data-test=\"api-method-trace-all-calls\"", markup);
Assert.Contains("Trace all calls (Flow Debugger)", markup);
Assert.Contains("7–10 audit rows per call", markup);
// Load, update and create paths all carry the flag.
Assert.Contains("_traceAllCalls = _existing.TraceAllCalls;", markup);
Assert.Contains("new UpdateApiMethodCommand(_existing.Id, _script, _timeoutSeconds, _params?.Trim(), _returns?.Trim(), _traceAllCalls)", markup);
Assert.Contains("new CreateApiMethodCommand(methodName, _script, _timeoutSeconds, _params?.Trim(), _returns?.Trim(), _traceAllCalls)", markup);
}
}