e1487fbf10
SecretEditor and ConfirmDeleteModal wrote the success audit inside the same try as the store write, so a throwing IAuditWriter after a committed add/rotate/delete showed an error, wrote a Failure audit and skipped the resolver-cache eviction - the old plaintext kept resolving for the TTL. The mutation try now covers only the cipher + store call; the success audit is best-effort afterwards and logs a warning (no value) on failure, so the change is reported saved, the callback fires and the cache is evicted. - tests: AuditFailureAfterCommitTests (add, rotate, delete) with a throwing success audit; all fail without the fix - code-reviews: Secrets-013 Resolved; README regenerated Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
37 lines
1.3 KiB
C#
37 lines
1.3 KiB
C#
using System.Collections.Concurrent;
|
|
using ZB.MOM.WW.Audit;
|
|
|
|
namespace ZB.MOM.WW.Secrets.Ui.Tests.Fakes;
|
|
|
|
/// <summary>
|
|
/// An <see cref="IAuditWriter"/> test double that collects every <see cref="AuditEvent"/> written,
|
|
/// so a test can assert on the audit trail (action, outcome, target, actor) and — critically — that
|
|
/// no plaintext ever appears in any audit field.
|
|
/// </summary>
|
|
public sealed class CapturingAuditWriter : IAuditWriter
|
|
{
|
|
private readonly ConcurrentQueue<AuditEvent> _events = new();
|
|
|
|
/// <summary>The events captured so far, in write order.</summary>
|
|
public IReadOnlyList<AuditEvent> Events => _events.ToArray();
|
|
|
|
/// <summary>
|
|
/// When set, every <see cref="AuditOutcome.Success"/> write throws this instead of being captured
|
|
/// — the "audit sink briefly unavailable after the store committed" case (scadaproj#34).
|
|
/// Failure-outcome writes are still captured, so a test can prove none was written.
|
|
/// </summary>
|
|
public Exception? SuccessFault { get; set; }
|
|
|
|
/// <inheritdoc />
|
|
public Task WriteAsync(AuditEvent evt, CancellationToken ct = default)
|
|
{
|
|
if (SuccessFault is not null && evt.Outcome == AuditOutcome.Success)
|
|
{
|
|
throw SuccessFault;
|
|
}
|
|
|
|
_events.Enqueue(evt);
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|