Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using Omika.Core.Observability.External;
|
||||
|
||||
namespace Omika.Tests.Observability.External;
|
||||
|
||||
/// <summary>Disjoncteur du pont externe (§16) : déterministe (horloge injectée).</summary>
|
||||
public class ExternalCircuitBreakerTests
|
||||
{
|
||||
[Fact]
|
||||
public void StartsHealthy()
|
||||
{
|
||||
var circuit = NewCircuit();
|
||||
|
||||
Assert.Equal(ExternalObservabilityState.Healthy, circuit.State);
|
||||
Assert.True(circuit.AllowExport());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OpensAfterThresholdFailures()
|
||||
{
|
||||
var circuit = NewCircuit(); // seuil = 3
|
||||
|
||||
circuit.RecordFailure();
|
||||
circuit.RecordFailure();
|
||||
Assert.Equal(ExternalObservabilityState.Degraded, circuit.State);
|
||||
Assert.True(circuit.AllowExport()); // dégradé mais autorisé
|
||||
|
||||
circuit.RecordFailure();
|
||||
Assert.Equal(ExternalObservabilityState.Open, circuit.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DropsWhileOpen()
|
||||
{
|
||||
var clock = new MutableClock();
|
||||
var circuit = new ExternalCircuitBreaker(2, TimeSpan.FromSeconds(30), clock.Now);
|
||||
circuit.RecordFailure();
|
||||
circuit.RecordFailure();
|
||||
|
||||
Assert.False(circuit.AllowExport()); // open : drop
|
||||
Assert.False(circuit.AllowExport());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProbesAfterCooldown()
|
||||
{
|
||||
var clock = new MutableClock();
|
||||
var circuit = new ExternalCircuitBreaker(1, TimeSpan.FromSeconds(30), clock.Now);
|
||||
circuit.RecordFailure();
|
||||
Assert.False(circuit.AllowExport());
|
||||
|
||||
clock.Advance(TimeSpan.FromSeconds(31));
|
||||
Assert.True(circuit.AllowExport()); // half-open : autorise UNE sonde
|
||||
Assert.Equal(ExternalObservabilityState.Degraded, circuit.State);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SuccessClosesCircuit()
|
||||
{
|
||||
var clock = new MutableClock();
|
||||
var circuit = new ExternalCircuitBreaker(1, TimeSpan.FromSeconds(30), clock.Now);
|
||||
circuit.RecordFailure();
|
||||
clock.Advance(TimeSpan.FromSeconds(31));
|
||||
Assert.True(circuit.AllowExport());
|
||||
|
||||
circuit.RecordSuccess();
|
||||
Assert.Equal(ExternalObservabilityState.Healthy, circuit.State);
|
||||
Assert.True(circuit.AllowExport());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProbeFailureReopensCircuit()
|
||||
{
|
||||
var clock = new MutableClock();
|
||||
var circuit = new ExternalCircuitBreaker(1, TimeSpan.FromSeconds(30), clock.Now);
|
||||
circuit.RecordFailure();
|
||||
clock.Advance(TimeSpan.FromSeconds(31));
|
||||
Assert.True(circuit.AllowExport()); // half-open sonde
|
||||
|
||||
circuit.RecordFailure(); // sonde échoue → ré-ouverture
|
||||
Assert.Equal(ExternalObservabilityState.Open, circuit.State);
|
||||
Assert.False(circuit.AllowExport());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NeverAllowsNegativeOrZeroCooldown()
|
||||
{
|
||||
var clock = new MutableClock();
|
||||
var circuit = new ExternalCircuitBreaker(1, TimeSpan.Zero, clock.Now);
|
||||
|
||||
circuit.RecordFailure();
|
||||
Assert.False(circuit.AllowExport()); // cooldown ramené à >= 1 s : encore ouvert
|
||||
|
||||
clock.Advance(TimeSpan.FromSeconds(2));
|
||||
Assert.True(circuit.AllowExport()); // sonde possible après cooldown positif
|
||||
}
|
||||
|
||||
private static ExternalCircuitBreaker NewCircuit() =>
|
||||
new(3, TimeSpan.FromSeconds(30));
|
||||
|
||||
private sealed class MutableClock
|
||||
{
|
||||
private DateTimeOffset _now = new(2025, 1, 1, 0, 0, 0, TimeSpan.Zero);
|
||||
|
||||
public DateTimeOffset Now() => _now;
|
||||
|
||||
public void Advance(TimeSpan delta) => _now += delta;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user