252 lines
8.9 KiB
C#
252 lines
8.9 KiB
C#
using SuiteLink.Client.Internal;
|
|
using SuiteLink.Client.Protocol;
|
|
|
|
namespace SuiteLink.Client.Tests.Internal;
|
|
|
|
public sealed class SuiteLinkSessionTests
|
|
{
|
|
[Fact]
|
|
public void NewSession_StartsDisconnected()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
|
|
Assert.Equal(SuiteLinkSessionState.Disconnected, session.State);
|
|
}
|
|
|
|
[Fact]
|
|
public void RegisterSubscription_TracksForwardAndReverseMappings()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
|
|
session.RegisterSubscription("Pump001.Run", 0x1234, _ => { });
|
|
|
|
Assert.True(session.TryGetTagId("Pump001.Run", out var tagId));
|
|
Assert.Equal(0x1234u, tagId);
|
|
Assert.True(session.TryGetItemName(0x1234, out var itemName));
|
|
Assert.Equal("Pump001.Run", itemName);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryDispatchUpdate_KnownTag_InvokesRegisteredCallback()
|
|
{
|
|
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 receivedAtUtc = new DateTimeOffset(2026, 03, 16, 18, 00, 00, TimeSpan.Zero);
|
|
var dispatched = session.TryDispatchUpdate(decoded, receivedAtUtc, out var dispatchedUpdate);
|
|
|
|
Assert.True(dispatched);
|
|
Assert.NotNull(dispatchedUpdate);
|
|
Assert.Equal("Pump001.Run", dispatchedUpdate.ItemName);
|
|
Assert.Equal(0x1234u, dispatchedUpdate.TagId);
|
|
Assert.Equal(0x00C0, dispatchedUpdate.Quality);
|
|
Assert.Equal(10, dispatchedUpdate.ElapsedMilliseconds);
|
|
Assert.Equal(receivedAtUtc, dispatchedUpdate.ReceivedAtUtc);
|
|
Assert.Equal(dispatchedUpdate, callbackUpdate);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryDispatchUpdate_UnknownTag_ReturnsFalseAndDoesNotInvokeCallback()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
var callbackCount = 0;
|
|
|
|
session.RegisterSubscription("Pump001.Run", 0x1234, _ => callbackCount++);
|
|
|
|
var decoded = new DecodedUpdate(
|
|
TagId: 0x9999,
|
|
Quality: 0x00C0,
|
|
ElapsedMilliseconds: 5,
|
|
Value: SuiteLinkValue.FromInt32(42));
|
|
|
|
var dispatched = session.TryDispatchUpdate(decoded, DateTimeOffset.UtcNow, out var dispatchedUpdate);
|
|
|
|
Assert.False(dispatched);
|
|
Assert.Null(dispatchedUpdate);
|
|
Assert.Equal(0, callbackCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnregisterByItemName_RemovesMappingsAndCallback()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
var callbackCount = 0;
|
|
|
|
session.RegisterSubscription("Pump001.Run", 0x1234, _ => callbackCount++);
|
|
|
|
Assert.True(session.TryUnregisterByItemName("Pump001.Run", out var removedTagId));
|
|
Assert.Equal(0x1234u, removedTagId);
|
|
Assert.False(session.TryGetTagId("Pump001.Run", out _));
|
|
Assert.False(session.TryGetItemName(0x1234, out _));
|
|
|
|
var decoded = new DecodedUpdate(
|
|
TagId: 0x1234,
|
|
Quality: 0x00C0,
|
|
ElapsedMilliseconds: 1,
|
|
Value: SuiteLinkValue.FromBoolean(true));
|
|
|
|
var dispatched = session.TryDispatchUpdate(decoded, DateTimeOffset.UtcNow, out _);
|
|
|
|
Assert.False(dispatched);
|
|
Assert.Equal(0, callbackCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void RegisterSubscription_SameItemName_ReplacesOldTagAndCallback()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
var oldCount = 0;
|
|
var newCount = 0;
|
|
|
|
session.RegisterSubscription("Pump001.Run", 0x1000, _ => oldCount++);
|
|
session.RegisterSubscription("Pump001.Run", 0x2000, _ => newCount++);
|
|
|
|
Assert.False(session.TryGetItemName(0x1000, out _));
|
|
Assert.True(session.TryGetTagId("Pump001.Run", out var currentTagId));
|
|
Assert.Equal(0x2000u, currentTagId);
|
|
|
|
var oldDecoded = new DecodedUpdate(0x1000, 0x00C0, 1, SuiteLinkValue.FromBoolean(true));
|
|
var newDecoded = new DecodedUpdate(0x2000, 0x00C0, 1, SuiteLinkValue.FromBoolean(true));
|
|
|
|
Assert.False(session.TryDispatchUpdate(oldDecoded, DateTimeOffset.UtcNow, out _));
|
|
Assert.True(session.TryDispatchUpdate(newDecoded, DateTimeOffset.UtcNow, out _));
|
|
Assert.Equal(0, oldCount);
|
|
Assert.Equal(1, newCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void RegisterSubscription_SameTagId_ReplacesOldItemAndCallback()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
var oldCount = 0;
|
|
var newCount = 0;
|
|
|
|
session.RegisterSubscription("Pump001.Run", 0x1234, _ => oldCount++);
|
|
session.RegisterSubscription("Pump002.Run", 0x1234, _ => newCount++);
|
|
|
|
Assert.False(session.TryGetTagId("Pump001.Run", out _));
|
|
Assert.True(session.TryGetTagId("Pump002.Run", out var replacementTagId));
|
|
Assert.Equal(0x1234u, replacementTagId);
|
|
|
|
var decoded = new DecodedUpdate(0x1234, 0x00C0, 1, SuiteLinkValue.FromBoolean(true));
|
|
Assert.True(session.TryDispatchUpdate(decoded, DateTimeOffset.UtcNow, out var dispatchedUpdate));
|
|
Assert.NotNull(dispatchedUpdate);
|
|
Assert.Equal("Pump002.Run", dispatchedUpdate.ItemName);
|
|
Assert.Equal(0, oldCount);
|
|
Assert.Equal(1, newCount);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryDispatchUpdate_CallbackThrows_IsCaughtAndReported()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
|
|
session.RegisterSubscription("Pump001.Run", 0x1234, _ => throw new InvalidOperationException("callback failure"));
|
|
|
|
var decoded = new DecodedUpdate(
|
|
TagId: 0x1234,
|
|
Quality: 0x00C0,
|
|
ElapsedMilliseconds: 5,
|
|
Value: SuiteLinkValue.FromInt32(42));
|
|
|
|
var dispatched = session.TryDispatchUpdate(
|
|
decoded,
|
|
DateTimeOffset.UtcNow,
|
|
out var dispatchedUpdate,
|
|
out var callbackException);
|
|
|
|
Assert.False(dispatched);
|
|
Assert.NotNull(dispatchedUpdate);
|
|
Assert.NotNull(callbackException);
|
|
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.Ready));
|
|
|
|
Assert.Contains("Invalid state transition", ex.Message);
|
|
Assert.Equal(SuiteLinkSessionState.Disconnected, session.State);
|
|
}
|
|
|
|
[Fact]
|
|
public void TryTransitionState_EnforcesExpectedCurrentStateAtomically()
|
|
{
|
|
var session = new SuiteLinkSession();
|
|
|
|
Assert.True(session.TryTransitionState(SuiteLinkSessionState.Disconnected, SuiteLinkSessionState.TcpConnected));
|
|
Assert.Equal(SuiteLinkSessionState.TcpConnected, session.State);
|
|
|
|
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);
|
|
}
|
|
}
|