feat: add resilient reconnect and catch-up replay

This commit is contained in:
Joseph Doherty
2026-03-17 11:04:19 -04:00
parent c278f98496
commit 2f04ec9d1d
29 changed files with 3746 additions and 95 deletions

View File

@@ -169,12 +169,56 @@ public sealed class SuiteLinkSessionTests
Assert.Equal("callback failure", callbackException.Message);
}
[Fact]
public void TryDispatchUpdate_WithExplicitSource_UsesProvidedSource()
{
var session = new SuiteLinkSession();
SuiteLinkTagUpdate? callbackUpdate = null;
session.RegisterSubscription("Pump001.Run", 0x1234, update => callbackUpdate = update);
var decoded = new DecodedUpdate(
TagId: 0x1234,
Quality: 0x00C0,
ElapsedMilliseconds: 10,
Value: SuiteLinkValue.FromBoolean(true));
var dispatched = session.TryDispatchUpdate(
decoded,
DateTimeOffset.UtcNow,
SuiteLinkUpdateSource.CatchUpReplay,
out var dispatchedUpdate,
out _);
Assert.True(dispatched);
Assert.NotNull(dispatchedUpdate);
Assert.Equal(SuiteLinkUpdateSource.CatchUpReplay, dispatchedUpdate.Source);
Assert.Equal(dispatchedUpdate, callbackUpdate);
}
[Fact]
public void ClearSubscriptions_RemovesAllMappings()
{
var session = new SuiteLinkSession();
session.RegisterSubscription("Pump001.Run", 0x1234, _ => { });
session.RegisterSubscription("Pump001.Speed", 0x5678, _ => { });
session.ClearSubscriptions();
Assert.False(session.TryGetTagId("Pump001.Run", out _));
Assert.False(session.TryGetTagId("Pump001.Speed", out _));
Assert.False(session.TryGetItemName(0x1234, out _));
Assert.False(session.TryGetItemName(0x5678, out _));
Assert.Equal(0, session.SubscriptionCount);
}
[Fact]
public void SetState_InvalidTransition_ThrowsInvalidOperationException()
{
var session = new SuiteLinkSession();
var ex = Assert.Throws<InvalidOperationException>(() => session.SetState(SuiteLinkSessionState.SessionConnected));
var ex = Assert.Throws<InvalidOperationException>(() => session.SetState(SuiteLinkSessionState.Ready));
Assert.Contains("Invalid state transition", ex.Message);
Assert.Equal(SuiteLinkSessionState.Disconnected, session.State);
@@ -191,4 +235,17 @@ public sealed class SuiteLinkSessionTests
Assert.False(session.TryTransitionState(SuiteLinkSessionState.Disconnected, SuiteLinkSessionState.HandshakeComplete));
Assert.Equal(SuiteLinkSessionState.TcpConnected, session.State);
}
[Fact]
public void SetState_ReconnectAttemptStartupFailure_CanReturnToReconnecting()
{
var session = new SuiteLinkSession();
session.SetState(SuiteLinkSessionState.TcpConnected);
session.SetState(SuiteLinkSessionState.HandshakeComplete);
session.SetState(SuiteLinkSessionState.ConnectSent);
session.SetState(SuiteLinkSessionState.Reconnecting);
Assert.Equal(SuiteLinkSessionState.Reconnecting, session.State);
}
}