79 lines
3.3 KiB
C#
79 lines
3.3 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Supervisor;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy.Tests;
|
|
|
|
[Trait("Category", "Unit")]
|
|
public sealed class CircuitBreakerTests
|
|
{
|
|
[Fact]
|
|
public void First_three_crashes_within_window_allow_respawn()
|
|
{
|
|
var breaker = new CircuitBreaker();
|
|
var t0 = new DateTime(2026, 4, 17, 12, 0, 0, DateTimeKind.Utc);
|
|
|
|
breaker.TryRecordCrash(t0, out _).ShouldBeTrue();
|
|
breaker.TryRecordCrash(t0.AddSeconds(30), out _).ShouldBeTrue();
|
|
breaker.TryRecordCrash(t0.AddSeconds(60), out _).ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Fourth_crash_within_window_opens_breaker_with_sticky_alert()
|
|
{
|
|
var breaker = new CircuitBreaker();
|
|
var t0 = new DateTime(2026, 4, 17, 12, 0, 0, DateTimeKind.Utc);
|
|
|
|
for (var i = 0; i < 3; i++) breaker.TryRecordCrash(t0.AddSeconds(i * 30), out _);
|
|
|
|
breaker.TryRecordCrash(t0.AddSeconds(120), out var remaining).ShouldBeFalse();
|
|
remaining.ShouldBe(TimeSpan.FromHours(1));
|
|
breaker.StickyAlertActive.ShouldBeTrue();
|
|
}
|
|
|
|
[Fact]
|
|
public void Cooldown_escalates_1h_then_4h_then_manual()
|
|
{
|
|
var breaker = new CircuitBreaker();
|
|
var t0 = new DateTime(2026, 4, 17, 12, 0, 0, DateTimeKind.Utc);
|
|
|
|
// Open once.
|
|
for (var i = 0; i < 4; i++) breaker.TryRecordCrash(t0.AddSeconds(i * 30), out _);
|
|
|
|
// Cooldown starts when the breaker opens (the 4th crash, at t0+90s). Jump past 1h from there.
|
|
var openedAt = t0.AddSeconds(90);
|
|
var afterFirstCooldown = openedAt.AddHours(1).AddMinutes(1);
|
|
breaker.TryRecordCrash(afterFirstCooldown, out _).ShouldBeTrue("cooldown elapsed, breaker closes for a try");
|
|
|
|
// Second trip: within 5 min, breaker opens again with 4h cooldown. The crash that trips
|
|
// it is the 3rd retry since the cooldown closed (afterFirstCooldown itself counted as 1).
|
|
breaker.TryRecordCrash(afterFirstCooldown.AddSeconds(30), out _).ShouldBeTrue();
|
|
breaker.TryRecordCrash(afterFirstCooldown.AddSeconds(60), out _).ShouldBeTrue();
|
|
breaker.TryRecordCrash(afterFirstCooldown.AddSeconds(90), out var cd2).ShouldBeFalse(
|
|
"4th crash within window reopens the breaker");
|
|
cd2.ShouldBe(TimeSpan.FromHours(4));
|
|
|
|
// Third trip: 4h elapsed, breaker closes for a try, then reopens with MaxValue (manual only).
|
|
var reopenedAt = afterFirstCooldown.AddSeconds(90);
|
|
var afterSecondCooldown = reopenedAt.AddHours(4).AddMinutes(1);
|
|
breaker.TryRecordCrash(afterSecondCooldown, out _).ShouldBeTrue();
|
|
breaker.TryRecordCrash(afterSecondCooldown.AddSeconds(30), out _).ShouldBeTrue();
|
|
breaker.TryRecordCrash(afterSecondCooldown.AddSeconds(60), out _).ShouldBeTrue();
|
|
breaker.TryRecordCrash(afterSecondCooldown.AddSeconds(90), out var cd3).ShouldBeFalse();
|
|
cd3.ShouldBe(TimeSpan.MaxValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void ManualReset_clears_sticky_alert_and_crash_history()
|
|
{
|
|
var breaker = new CircuitBreaker();
|
|
var t0 = DateTime.UtcNow;
|
|
for (var i = 0; i < 4; i++) breaker.TryRecordCrash(t0.AddSeconds(i * 30), out _);
|
|
|
|
breaker.ManualReset();
|
|
breaker.StickyAlertActive.ShouldBeFalse();
|
|
|
|
breaker.TryRecordCrash(t0.AddMinutes(10), out _).ShouldBeTrue();
|
|
}
|
|
}
|