using Google.Protobuf; using ZB.MOM.WW.MxGateway.Client; using ZB.MOM.WW.MxGateway.Contracts.Proto; namespace ZB.MOM.WW.MxGateway.Client.Tests; public sealed class MxCommandReplyExtensionsTests { /// Verifies that successful replies pass both protocol and MxAccess success checks. [Fact] public void EnsureSuccess_WithRegisterFixture_ReturnsReply() { MxCommandReply reply = ReadReplyFixture("register.ok.reply.json"); Assert.Same(reply, reply.EnsureProtocolSuccess()); Assert.Same(reply, reply.EnsureMxAccessSuccess()); } /// Verifies that MxAccess failures throw with preserved HResult and status details. [Fact] public void EnsureMxAccessSuccess_WithFailureFixture_PreservesHResultAndStatuses() { MxCommandReply reply = ReadReplyFixture("write.mxaccess-failure.reply.json"); reply.EnsureProtocolSuccess(); MxAccessException exception = Assert.Throws( reply.EnsureMxAccessSuccess); Assert.Equal(-2147220992, exception.HResultCode); Assert.Equal(reply.Statuses.Count, exception.Statuses.Count); Assert.Equal(reply, exception.Reply); Assert.Contains("0x80040200", exception.Message); } /// Verifies that a non-OK status category fails even when the raw success member is set. [Fact] public void EnsureMxAccessSuccess_WithNonOkCategoryAndSuccessSet_Throws() { MxCommandReply reply = ReadReplyFixture( "write.status-category-error-success-set.reply.json"); reply.EnsureProtocolSuccess(); MxAccessException exception = Assert.Throws( reply.EnsureMxAccessSuccess); Assert.Equal(1, Assert.Single(exception.Statuses).Success); Assert.Contains("CommunicationError", exception.Message, StringComparison.Ordinal); } /// Verifies that an Ok status category succeeds even when the raw success member is zero. [Fact] public void EnsureMxAccessSuccess_WithOkCategoryAndZeroSuccess_ReturnsReply() { MxCommandReply reply = ReadReplyFixture( "write.status-category-ok-success-zero.reply.json"); Assert.Equal(0, Assert.Single(reply.Statuses).Success); Assert.Same(reply, reply.EnsureProtocolSuccess()); Assert.Same(reply, reply.EnsureMxAccessSuccess()); } /// Verifies that a positive HResult (S_FALSE) is a COM success code, not a failure. [Fact] public void EnsureMxAccessSuccess_WithPositiveHResult_ReturnsReply() { MxCommandReply reply = ReadReplyFixture("write.hresult-s-false.reply.json"); Assert.Equal(1, reply.Hresult); Assert.Same(reply, reply.EnsureProtocolSuccess()); Assert.Same(reply, reply.EnsureMxAccessSuccess()); } /// Verifies that a negative HResult fails even when every status entry is Ok. [Fact] public void EnsureMxAccessSuccess_WithNegativeHResult_Throws() { MxCommandReply reply = ReadReplyFixture("write.hresult-e-fail.reply.json"); reply.EnsureProtocolSuccess(); MxAccessException exception = Assert.Throws( reply.EnsureMxAccessSuccess); Assert.Equal(-2147467259, exception.HResultCode); } /// Verifies that session-not-found protocol failures throw the correct gateway exception. [Fact] public void EnsureProtocolSuccess_WithSessionFailure_ThrowsSessionException() { MxCommandReply reply = new() { SessionId = "session-missing", CorrelationId = "correlation", ProtocolStatus = new ProtocolStatus { Code = ProtocolStatusCode.SessionNotFound, Message = "Session was not found.", }, }; MxGatewaySessionException exception = Assert.Throws( reply.EnsureProtocolSuccess); Assert.Equal("session-missing", exception.SessionId); Assert.Equal(ProtocolStatusCode.SessionNotFound, exception.ProtocolStatus?.Code); } private static MxCommandReply ReadReplyFixture(string fileName) { DirectoryInfo directory = new(AppContext.BaseDirectory); while (directory is not null) { string path = Path.Combine( directory.FullName, "clients", "proto", "fixtures", "behavior", "command-replies", fileName); if (File.Exists(path)) { return JsonParser.Default.Parse(File.ReadAllText(path)); } directory = directory.Parent!; } throw new FileNotFoundException(fileName); } }