Files
suitelinkclient/tests/SuiteLink.Client.Tests/Internal/SuiteLinkSessionTests.cs
2026-03-16 16:46:32 -04:00

195 lines
7.0 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 SetState_InvalidTransition_ThrowsInvalidOperationException()
{
var session = new SuiteLinkSession();
var ex = Assert.Throws<InvalidOperationException>(() => session.SetState(SuiteLinkSessionState.SessionConnected));
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);
}
}