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); } }