fix(ui): preserve precise audit submissions and adopt delivered secrets

This commit is contained in:
Joseph Doherty
2026-11-13 19:42:53 -05:00
parent 9dbfe7bcaf
commit d370dbd37c
80 changed files with 12778 additions and 77 deletions
@@ -57,6 +57,103 @@ public class AuditFilterBarTests : BunitContext
Services.AddSingleton(_auditLogQueryService);
}
/// <summary>Malformed exact identifiers never silently broaden a submitted query.</summary>
[Theory]
[InlineData("audit-execution-id")]
[InlineData("audit-parent-execution-id")]
public void MalformedExactId_PreservesInputAndLinkedErrorWithoutCallback(string id)
{
var calls = 0;
var cut = Render<AuditFilterBar>(p => p.Add(c => c.OnFilterChanged, _ => calls++));
cut.Find("#" + id).Change("not-a-guid");
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.Equal(0, calls);
var input = cut.Find("#" + id);
Assert.Equal("not-a-guid", input.GetAttribute("value"));
Assert.Equal("true", input.GetAttribute("aria-invalid"));
var errorId = input.GetAttribute("aria-describedby");
Assert.False(string.IsNullOrWhiteSpace(errorId));
Assert.Contains("valid GUID", cut.Find("#" + errorId).TextContent);
input.Change(Guid.NewGuid().ToString());
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.Equal(1, calls);
Assert.Null(cut.Find("#" + id).GetAttribute("aria-invalid"));
}
/// <summary>Submitted hidden constraints remain visible, and changed drafts are distinguished.</summary>
[Fact]
public void MoreFilters_SummaryRetainsSubmittedConstraintsAndClearOnlyResetsDraft()
{
var calls = 0;
var cut = Render<AuditFilterBar>(p => p.Add(c => c.OnFilterChanged, _ => calls++));
cut.Find("#audit-actor").Change("operator-a");
cut.Find("#audit-target").Change("system-b");
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.Equal(1, calls);
Assert.Contains("Actor: operator-a", cut.Find("[data-test=filter-submitted-summary]").TextContent);
Assert.Contains("Target: system-b", cut.Find("[data-test=filter-submitted-summary]").TextContent);
Assert.Single(cut.FindAll("details[data-test=filter-more]"));
Assert.Empty(cut.FindAll("[data-test=filter-pending]"));
cut.Find("#audit-actor").Change("draft-only");
Assert.Contains("Unsubmitted changes", cut.Find("[data-test=filter-pending]").TextContent);
Assert.DoesNotContain("draft-only", cut.Find("[data-test=filter-submitted-summary]").TextContent);
cut.Find("[data-test=filter-clear]").Click();
Assert.Equal(string.Empty, cut.Find("#audit-actor").GetAttribute("value"));
Assert.Equal(string.Empty, cut.Find("#audit-target").GetAttribute("value"));
Assert.Contains("Actor: operator-a", cut.Find("[data-test=filter-submitted-summary]").TextContent);
Assert.Equal(1, calls);
}
/// <summary>A child selection change makes the parent draft visibly pending.</summary>
[Fact]
public void MoreFilters_NodeChangeUpdatesDraftCountAndPendingState()
{
var cut = Render<AuditFilterBar>();
cut.Find("form[data-test=audit-filter-bar]").Submit();
cut.Find("[data-test=filter-node-ms-opt-central-a]").Change(true);
Assert.Contains("Unsubmitted changes", cut.Find("[data-test=filter-pending]").TextContent);
Assert.Contains("1 field set", cut.Find("details[data-test=filter-more] summary").TextContent);
}
/// <summary>Clear removes invalid exact-id state as well as every advanced draft value.</summary>
[Fact]
public void Clear_RemovesAdvancedValuesAndValidationErrors()
{
var cut = Render<AuditFilterBar>();
var fields = new[] { "audit-instance", "audit-script", "audit-target", "audit-actor", "audit-execution-id", "audit-parent-execution-id" };
foreach (var id in fields) cut.Find("#" + id).Change("invalid-draft");
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.Equal(2, cut.FindAll("[role=alert]").Count);
cut.Find("[data-test=filter-clear]").Click();
foreach (var id in fields) Assert.Equal(string.Empty, cut.Find("#" + id).GetAttribute("value"));
Assert.Empty(cut.FindAll("[role=alert]"));
Assert.Empty(cut.FindAll("input[aria-invalid=true]"));
Assert.Contains("0 fields set", cut.Find("details[data-test=filter-more] summary").TextContent);
}
/// <summary>Out-of-order callback completion cannot replace the most recently submitted draft.</summary>
[Fact]
public async Task Apply_OutOfOrderCallbacksRetainLatestSubmittedSummary()
{
var entered = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var pending = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
var cut = Render<AuditFilterBar>(p => p.Add(c => c.OnFilterChanged, async filter =>
{
if (filter.Actor == "A") { entered.TrySetResult(true); await pending.Task; }
}));
cut.Find("#audit-actor").Change("A");
var first = cut.Find("form[data-test=audit-filter-bar]").TriggerEventAsync("onsubmit", EventArgs.Empty);
await entered.Task;
cut.Find("#audit-actor").Change("B");
await cut.Find("form[data-test=audit-filter-bar]").TriggerEventAsync("onsubmit", EventArgs.Empty);
Assert.Contains("Actor: B", cut.Find("[data-test=filter-submitted-summary]").TextContent);
await cut.InvokeAsync(() => pending.TrySetResult(true));
await first;
Assert.Contains("Actor: B", cut.Find("[data-test=filter-submitted-summary]").TextContent);
Assert.DoesNotContain("Actor: A", cut.Find("[data-test=filter-submitted-summary]").TextContent);
Assert.Empty(cut.FindAll("[data-test=filter-pending]"));
}
/// <summary>UTC-labelled custom picks are identical in every process timezone.</summary>
[Theory]
[InlineData("2026-11-12T10:00", "2026-11-12T11:00")]
@@ -302,24 +399,16 @@ public class AuditFilterBarTests : BunitContext
Assert.Equal(executionId, captured!.ExecutionId);
}
/// <summary>Verifies that a blank or unparseable execution id leaves the filter execution id null.</summary>
/// <summary>Whitespace remains an intentional unconstrained ExecutionId field.</summary>
[Fact]
public void Apply_WithBlankOrUnparseableExecutionId_LeavesFilterExecutionIdNull()
public void Apply_WithBlankExecutionId_LeavesFilterExecutionIdNull()
{
// Lax parsing: a blank box yields no constraint; garbage text likewise.
AuditLogQueryFilter? captured = null;
var cut = Render<AuditFilterBar>(p => p
.Add(c => c.OnFilterChanged, EventCallback.Factory.Create<AuditLogQueryFilter>(this, f => captured = f)));
// Blank — never typed into.
var cut = Render<AuditFilterBar>(p => p.Add(c => c.OnFilterChanged, f => captured = f));
cut.Find("#audit-execution-id").Change(" ");
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.NotNull(captured);
Assert.Null(captured!.ExecutionId);
// Unparseable paste — still dropped, no error.
cut.Find("[data-test=\"filter-execution-id\"] input").Change("not-a-guid");
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.Null(captured!.ExecutionId);
Assert.Null(captured.ExecutionId);
}
/// <summary>Verifies that a pasted parent execution id is mapped through to the filter on apply.</summary>
@@ -340,24 +429,16 @@ public class AuditFilterBarTests : BunitContext
Assert.Equal(parentExecutionId, captured!.ParentExecutionId);
}
/// <summary>Verifies that a blank or unparseable parent execution id leaves the filter parent execution id null.</summary>
/// <summary>Whitespace remains an intentional unconstrained ParentExecutionId field.</summary>
[Fact]
public void Apply_WithBlankOrUnparseableParentExecutionId_LeavesFilterParentExecutionIdNull()
public void Apply_WithBlankParentExecutionId_LeavesFilterParentExecutionIdNull()
{
// Lax parsing: a blank box yields no constraint; garbage text likewise.
AuditLogQueryFilter? captured = null;
var cut = Render<AuditFilterBar>(p => p
.Add(c => c.OnFilterChanged, EventCallback.Factory.Create<AuditLogQueryFilter>(this, f => captured = f)));
// Blank — never typed into.
var cut = Render<AuditFilterBar>(p => p.Add(c => c.OnFilterChanged, f => captured = f));
cut.Find("#audit-parent-execution-id").Change(" ");
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.NotNull(captured);
Assert.Null(captured!.ParentExecutionId);
// Unparseable paste — still dropped, no error.
cut.Find("[data-test=\"filter-parent-execution-id\"] input").Change("not-a-guid");
cut.Find("form[data-test=audit-filter-bar]").Submit();
Assert.Null(captured!.ParentExecutionId);
Assert.Null(captured.ParentExecutionId);
}
/// <summary>Verifies that choosing the last-hour time range sets the from-UTC value to approximately one hour ago.</summary>