using ZB.MOM.WW.Secrets.Abstractions; namespace ZB.MOM.WW.Secrets.Replicator.AkkaDotNet.Tests.Fakes; /// /// Wraps a store so every operation genuinely yields the thread before completing. /// /// /// /// This exists to close a blind spot that let a real bug through. A local SQLite store is fast /// enough that await on it frequently completes synchronously, which leaves the /// caller on the actor's own mailbox thread — and therefore leaves the actor context (a /// [ThreadStatic]) intact. Code that illegally touches actor context after an await /// then works by accident, and the whole suite passes. /// /// /// Under a slower store, a contended one, or simply a differently-timed thread pool, the same /// await goes properly asynchronous, the continuation lands on a pool thread with no actor /// context, and the code throws. That is the worst shape of bug: green in tests, broken in /// production, timing-dependent either way. Forcing the async path makes it deterministic. /// /// /// The store to wrap. public sealed class GenuinelyAsyncSecretStore(ISecretStore inner) : ISecretStore { private static async Task YieldRealAsync() { // Task.Yield alone can still be scheduled inline by some schedulers; a real (if tiny) delay // guarantees the continuation is posted to the thread pool. await Task.Delay(5).ConfigureAwait(false); await Task.Yield(); } /// public async Task GetAsync(SecretName name, CancellationToken ct) { await YieldRealAsync().ConfigureAwait(false); return await inner.GetAsync(name, ct).ConfigureAwait(false); } /// public async Task UpsertAsync(StoredSecret row, CancellationToken ct) { await YieldRealAsync().ConfigureAwait(false); await inner.UpsertAsync(row, ct).ConfigureAwait(false); } /// public async Task DeleteAsync(SecretName name, string? actor, CancellationToken ct) { await YieldRealAsync().ConfigureAwait(false); return await inner.DeleteAsync(name, actor, ct).ConfigureAwait(false); } /// public async Task> ListAsync(bool includeDeleted, CancellationToken ct) { await YieldRealAsync().ConfigureAwait(false); return await inner.ListAsync(includeDeleted, ct).ConfigureAwait(false); } /// public async Task> GetManifestAsync(CancellationToken ct) { await YieldRealAsync().ConfigureAwait(false); return await inner.GetManifestAsync(ct).ConfigureAwait(false); } /// public async Task ApplyReplicatedAsync(StoredSecret row, CancellationToken ct) { await YieldRealAsync().ConfigureAwait(false); await inner.ApplyReplicatedAsync(row, ct).ConfigureAwait(false); } /// public async Task ApplyRewrapAsync( StoredSecret rewrappedRow, byte[] expectedCurrentWrappedDek, CancellationToken ct) { await YieldRealAsync().ConfigureAwait(false); return await inner.ApplyRewrapAsync(rewrappedRow, expectedCurrentWrappedDek, ct) .ConfigureAwait(false); } }