namespace Books.Api.Tests.Helpers; /// /// Helper for testing eventually consistent systems. /// Polls a condition until it's met or times out. /// public static class Eventually { /// /// Polls a condition until it returns true or times out. /// public static async Task AssertAsync( Func> condition, TimeSpan? timeout = null, TimeSpan? pollInterval = null, string? failMessage = null) { timeout ??= TimeSpan.FromSeconds(5); pollInterval ??= TimeSpan.FromMilliseconds(50); var start = DateTime.UtcNow; while (DateTime.UtcNow - start < timeout) { if (await condition()) return; await Task.Delay(pollInterval.Value); } throw new TimeoutException( failMessage ?? $"Condition was not met within {timeout}."); } /// /// Polls until a non-null result is returned or times out. /// public static async Task GetAsync( Func> getter, TimeSpan? timeout = null, TimeSpan? pollInterval = null, string? failMessage = null) where T : class { timeout ??= TimeSpan.FromSeconds(10); pollInterval ??= TimeSpan.FromMilliseconds(100); var start = DateTime.UtcNow; while (DateTime.UtcNow - start < timeout) { var result = await getter(); if (result != null) return result; await Task.Delay(pollInterval.Value); } throw new TimeoutException( failMessage ?? $"Expected non-null result was not returned within {timeout}."); } /// /// Polls until a collection has the expected count or times out. /// public static async Task> GetListAsync( Func>> getter, int expectedCount, TimeSpan? timeout = null, TimeSpan? pollInterval = null, string? failMessage = null) { timeout ??= TimeSpan.FromSeconds(10); pollInterval ??= TimeSpan.FromMilliseconds(100); var start = DateTime.UtcNow; while (DateTime.UtcNow - start < timeout) { var result = await getter(); if (result.Count >= expectedCount) return result; await Task.Delay(pollInterval.Value); } throw new TimeoutException( failMessage ?? $"Expected {expectedCount} items but condition was not met within {timeout}."); } }