using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using MessagePack;
using Serilog;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Backend;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Ipc;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Shared.Contracts;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Host.Tests
{
///
/// Validates that correctly dispatches each
/// to the corresponding
/// method and serializes the response into the expected response kind. Uses
/// so no hardware is needed.
///
[Trait("Category", "Unit")]
public sealed class FwlibFrameHandlerTests
{
private static async Task RoundTripAsync(
IFrameHandler handler, FocasMessageKind reqKind, TReq req, FocasMessageKind expectedRespKind,
Action assertResponse)
{
using var buffer = new MemoryStream();
using var writer = new FrameWriter(buffer, leaveOpen: true);
await handler.HandleAsync(reqKind, MessagePackSerializer.Serialize(req), writer, CancellationToken.None);
buffer.Position = 0;
using var reader = new FrameReader(buffer, leaveOpen: true);
var frame = await reader.ReadFrameAsync(CancellationToken.None);
frame.HasValue.ShouldBeTrue();
frame!.Value.Kind.ShouldBe(expectedRespKind);
assertResponse(MessagePackSerializer.Deserialize(frame.Value.Body));
}
private static FwlibFrameHandler BuildHandler() =>
new(new FakeFocasBackend(), new LoggerConfiguration().CreateLogger());
[Fact]
public async Task OpenSession_returns_a_new_session_id()
{
long sessionId = 0;
await RoundTripAsync(
BuildHandler(),
FocasMessageKind.OpenSessionRequest,
new OpenSessionRequest { HostAddress = "h:8193" },
FocasMessageKind.OpenSessionResponse,
resp => { resp.Success.ShouldBeTrue(); resp.SessionId.ShouldBeGreaterThan(0L); sessionId = resp.SessionId; });
sessionId.ShouldBeGreaterThan(0L);
}
[Fact]
public async Task Read_without_open_session_returns_internal_error()
{
await RoundTripAsync(
BuildHandler(),
FocasMessageKind.ReadRequest,
new ReadRequest
{
SessionId = 999,
Address = new FocasAddressDto { Kind = 0, PmcLetter = "R", Number = 100 },
DataType = FocasDataTypeCode.Int32,
},
FocasMessageKind.ReadResponse,
resp => { resp.Success.ShouldBeFalse(); resp.Error.ShouldContain("session-not-open"); });
}
[Fact]
public async Task Full_open_write_read_round_trip_preserves_value()
{
var handler = BuildHandler();
// Open.
using var buffer = new MemoryStream();
using var writer = new FrameWriter(buffer, leaveOpen: true);
await handler.HandleAsync(FocasMessageKind.OpenSessionRequest,
MessagePackSerializer.Serialize(new OpenSessionRequest { HostAddress = "h:8193" }), writer, CancellationToken.None);
buffer.Position = 0;
using var reader = new FrameReader(buffer, leaveOpen: true);
var openFrame = await reader.ReadFrameAsync(CancellationToken.None);
var openResp = MessagePackSerializer.Deserialize(openFrame!.Value.Body);
var sessionId = openResp.SessionId;
// Write 42 at MACRO:500 as Int32.
buffer.Position = 0;
buffer.SetLength(0);
await handler.HandleAsync(FocasMessageKind.WriteRequest,
MessagePackSerializer.Serialize(new WriteRequest
{
SessionId = sessionId,
Address = new FocasAddressDto { Kind = 2, Number = 500 },
DataType = FocasDataTypeCode.Int32,
ValueTypeCode = FocasDataTypeCode.Int32,
ValueBytes = MessagePackSerializer.Serialize((int)42),
}), writer, CancellationToken.None);
// Read back.
buffer.Position = 0;
buffer.SetLength(0);
await handler.HandleAsync(FocasMessageKind.ReadRequest,
MessagePackSerializer.Serialize(new ReadRequest
{
SessionId = sessionId,
Address = new FocasAddressDto { Kind = 2, Number = 500 },
DataType = FocasDataTypeCode.Int32,
}), writer, CancellationToken.None);
buffer.Position = 0;
var readFrame = await reader.ReadFrameAsync(CancellationToken.None);
readFrame.HasValue.ShouldBeTrue();
readFrame!.Value.Kind.ShouldBe(FocasMessageKind.ReadResponse);
// With buffer reuse there may be multiple queued frames; we want the last one.
var lastResp = MessagePackSerializer.Deserialize(readFrame.Value.Body);
// If the Write frame is first, drain it.
if (lastResp.ValueBytes is null)
{
var next = await reader.ReadFrameAsync(CancellationToken.None);
lastResp = MessagePackSerializer.Deserialize(next!.Value.Body);
}
lastResp.Success.ShouldBeTrue();
MessagePackSerializer.Deserialize(lastResp.ValueBytes!).ShouldBe(42);
}
[Fact]
public async Task PmcBitWrite_sets_specified_bit()
{
var handler = BuildHandler();
using var buffer = new MemoryStream();
using var writer = new FrameWriter(buffer, leaveOpen: true);
await handler.HandleAsync(FocasMessageKind.OpenSessionRequest,
MessagePackSerializer.Serialize(new OpenSessionRequest { HostAddress = "h:8193" }), writer, CancellationToken.None);
buffer.Position = 0;
using var reader = new FrameReader(buffer, leaveOpen: true);
var openFrame = await reader.ReadFrameAsync(CancellationToken.None);
var sessionId = MessagePackSerializer.Deserialize(openFrame!.Value.Body).SessionId;
buffer.Position = 0; buffer.SetLength(0);
await handler.HandleAsync(FocasMessageKind.PmcBitWriteRequest,
MessagePackSerializer.Serialize(new PmcBitWriteRequest
{
SessionId = sessionId,
Address = new FocasAddressDto { Kind = 0, PmcLetter = "R", Number = 100 },
BitIndex = 3,
Value = true,
}), writer, CancellationToken.None);
buffer.Position = 0;
var resp = MessagePackSerializer.Deserialize(
(await reader.ReadFrameAsync(CancellationToken.None))!.Value.Body);
resp.Success.ShouldBeTrue();
resp.StatusCode.ShouldBe(0u);
}
[Fact]
public async Task Probe_reports_healthy_when_session_open()
{
var handler = BuildHandler();
using var buffer = new MemoryStream();
using var writer = new FrameWriter(buffer, leaveOpen: true);
await handler.HandleAsync(FocasMessageKind.OpenSessionRequest,
MessagePackSerializer.Serialize(new OpenSessionRequest { HostAddress = "h:8193" }), writer, CancellationToken.None);
buffer.Position = 0;
using var reader = new FrameReader(buffer, leaveOpen: true);
var sessionId = MessagePackSerializer.Deserialize(
(await reader.ReadFrameAsync(CancellationToken.None))!.Value.Body).SessionId;
buffer.Position = 0; buffer.SetLength(0);
await handler.HandleAsync(FocasMessageKind.ProbeRequest,
MessagePackSerializer.Serialize(new ProbeRequest { SessionId = sessionId }), writer, CancellationToken.None);
buffer.Position = 0;
var resp = MessagePackSerializer.Deserialize(
(await reader.ReadFrameAsync(CancellationToken.None))!.Value.Body);
resp.Healthy.ShouldBeTrue();
}
[Fact]
public async Task Unconfigured_backend_returns_pointed_error_message()
{
var handler = new FwlibFrameHandler(new UnconfiguredFocasBackend(), new LoggerConfiguration().CreateLogger());
await RoundTripAsync(
handler,
FocasMessageKind.OpenSessionRequest,
new OpenSessionRequest { HostAddress = "h:8193" },
FocasMessageKind.OpenSessionResponse,
resp =>
{
resp.Success.ShouldBeFalse();
resp.Error.ShouldContain("Fwlib32");
resp.ErrorCode.ShouldBe("NoFwlibBackend");
});
}
}
}