using Microsoft.Extensions.Caching.Memory; using MongoDB.Entities; using PrivaPub.Infrastructure.Jobs; using PrivaPub.Models.Jobs; using PrivaPub.Tests.Support; namespace PrivaPub.Tests.Infrastructure { [Trait("Category", "Integration")] public sealed class HostCircuitBreakerTests : IAsyncLifetime { readonly string _host = $"breaker{Guid.NewGuid():N}.example"; public ValueTask InitializeAsync() { Assert.SkipUnless(MongoFixture.Enabled, MongoFixture.Skip); return ValueTask.CompletedTask; } public async ValueTask DisposeAsync() { if (MongoFixture.Enabled) await DB.Default.DeleteAsync(i => i.Host == _host); } [Fact] public async Task A_host_is_quarantined_only_past_the_threshold_and_freed_by_a_success() { var token = TestContext.Current.CancellationToken; var breaker = new HostCircuitBreaker(new MemoryCache(new MemoryCacheOptions())); for (var i = 1; i < HostCircuitBreaker.Threshold; i++) await breaker.Failed(_host, "503", token); Assert.Null(await breaker.UnavailableUntil(_host, token)); await breaker.Failed(_host, "503", token); var until = await breaker.UnavailableUntil(_host, token); Assert.NotNull(until); Assert.InRange(until.Value, DateTime.UtcNow.AddMinutes(55), DateTime.UtcNow.AddMinutes(65)); var instance = await DB.Default.Find().Match(i => i.Host == _host).ExecuteFirstAsync(token); Assert.Equal(HostCircuitBreaker.Threshold, instance.ConsecutiveFailures); Assert.Equal("503", instance.LastError); await breaker.Succeeded(_host, token); Assert.Null(await breaker.UnavailableUntil(_host, token)); instance = await DB.Default.Find().Match(i => i.Host == _host).ExecuteFirstAsync(token); Assert.Equal(0, instance.ConsecutiveFailures); Assert.NotNull(instance.LastSuccessAt); } [Fact] public async Task A_healthy_host_is_written_at_most_once_an_hour() { var token = TestContext.Current.CancellationToken; var breaker = new HostCircuitBreaker(new MemoryCache(new MemoryCacheOptions())); await breaker.Succeeded(_host, token); var first = (await DB.Default.Find().Match(i => i.Host == _host).ExecuteFirstAsync(token)).LastSuccessAt; await breaker.Succeeded(_host, token); var second = (await DB.Default.Find().Match(i => i.Host == _host).ExecuteFirstAsync(token)).LastSuccessAt; Assert.Equal(first, second); } } }