201 lines
9.2 KiB
C#
201 lines
9.2 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Validates that <see cref="FwlibFrameHandler"/> correctly dispatches each
|
|
/// <see cref="FocasMessageKind"/> to the corresponding <see cref="IFocasBackend"/>
|
|
/// method and serializes the response into the expected response kind. Uses
|
|
/// <see cref="FakeFocasBackend"/> so no hardware is needed.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class FwlibFrameHandlerTests
|
|
{
|
|
private static async Task RoundTripAsync<TReq, TResp>(
|
|
IFrameHandler handler, FocasMessageKind reqKind, TReq req, FocasMessageKind expectedRespKind,
|
|
Action<TResp> 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<TResp>(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<OpenSessionRequest, OpenSessionResponse>(
|
|
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<ReadRequest, ReadResponse>(
|
|
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<OpenSessionResponse>(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<ReadResponse>(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<ReadResponse>(next!.Value.Body);
|
|
}
|
|
lastResp.Success.ShouldBeTrue();
|
|
MessagePackSerializer.Deserialize<int>(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<OpenSessionResponse>(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<PmcBitWriteResponse>(
|
|
(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<OpenSessionResponse>(
|
|
(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<ProbeResponse>(
|
|
(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<OpenSessionRequest, OpenSessionResponse>(
|
|
handler,
|
|
FocasMessageKind.OpenSessionRequest,
|
|
new OpenSessionRequest { HostAddress = "h:8193" },
|
|
FocasMessageKind.OpenSessionResponse,
|
|
resp =>
|
|
{
|
|
resp.Success.ShouldBeFalse();
|
|
resp.Error.ShouldContain("Fwlib32");
|
|
resp.ErrorCode.ShouldBe("NoFwlibBackend");
|
|
});
|
|
}
|
|
}
|
|
}
|