From f451d7e0862216d2525ab961120d62802f7387bd Mon Sep 17 00:00:00 2001 From: Cristion Dominguez Date: Tue, 3 Dec 2024 11:29:21 -0700 Subject: [PATCH] Finish unit tests --- .../Commands/SendExtendedRequestCommand.cs | 4 +- .../Commands/SendTelemetryRequestCommand.cs | 22 +- .../{ => Tests}/BillValidatorTests.cs | 317 ++++++++++++------ .../Utility/MoqExtensions.cs | 47 +++ .../BillValidators/BillValidator.Extended.cs | 9 +- .../BillValidators/BillValidator.Telemetry.cs | 72 ++-- .../BillValidators/BillValidator.cs | 146 ++++---- PTI.Rs232Validator/Messages/Rs232Message.cs | 2 +- .../Views/MainWindow.Extended.cs | 4 +- .../Views/MainWindow.Telemetry.cs | 22 +- 10 files changed, 411 insertions(+), 234 deletions(-) rename PTI.Rs232Validator.Test/{ => Tests}/BillValidatorTests.cs (70%) create mode 100644 PTI.Rs232Validator.Test/Utility/MoqExtensions.cs diff --git a/PTI.Rs232Validator.CLI/Commands/SendExtendedRequestCommand.cs b/PTI.Rs232Validator.CLI/Commands/SendExtendedRequestCommand.cs index 74e5379..efad40d 100644 --- a/PTI.Rs232Validator.CLI/Commands/SendExtendedRequestCommand.cs +++ b/PTI.Rs232Validator.CLI/Commands/SendExtendedRequestCommand.cs @@ -36,11 +36,11 @@ public override int Execute(CommandContext context, Settings settings) { case ExtendedCommand.BarcodeDetected: var responseMessage = billValidator.GetDetectedBarcode().Result; - if (!string.IsNullOrEmpty(responseMessage?.Barcode)) + if (responseMessage is { IsValid: true, Barcode.Length: > 0 }) { commandLogger.LogInfo($"The barcode is: {responseMessage.Barcode}"); } - else if (responseMessage is not null) + else if (responseMessage is { IsValid: true, Barcode.Length: 0 }) { commandLogger.LogInfo("No barcode was detected since the last power cycle."); } diff --git a/PTI.Rs232Validator.Cli/Commands/SendTelemetryRequestCommand.cs b/PTI.Rs232Validator.Cli/Commands/SendTelemetryRequestCommand.cs index 248e2b6..d714626 100644 --- a/PTI.Rs232Validator.Cli/Commands/SendTelemetryRequestCommand.cs +++ b/PTI.Rs232Validator.Cli/Commands/SendTelemetryRequestCommand.cs @@ -38,7 +38,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.Ping: { var responseMessage = billValidator.PingAsync().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo("Successfully pinged the acceptor."); } @@ -53,11 +53,11 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.GetSerialNumber: { var responseMessage = billValidator.GetSerialNumberAsync().Result; - if (!string.IsNullOrEmpty(responseMessage?.SerialNumber)) + if (responseMessage is { IsValid: true, SerialNumber.Length: > 0 }) { commandLogger.LogInfo($"The serial number is: {responseMessage.SerialNumber}"); } - else if (responseMessage?.SerialNumber is not null) + else if (responseMessage is { IsValid: true, SerialNumber.Length: 0 }) { commandLogger.LogInfo("The was not assigned a serial number."); } @@ -72,7 +72,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.GetCashboxMetrics: { var responseMessage = billValidator.GetCashboxMetrics().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo($"The cashbox metrics are as follows — {responseMessage}"); } @@ -87,7 +87,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.ClearCashboxCount: { var responseMessage = billValidator.ClearCashboxCount().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo("Successfully cleared the cashbox count."); } @@ -102,7 +102,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.GetUnitMetrics: { var responseMessage = billValidator.GetUnitMetrics().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo($"The unit metrics are as follows — {responseMessage}"); } @@ -117,7 +117,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.GetServiceUsageCounters: { var responseMessage = billValidator.GetServiceUsageCounters().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo($"The service usage counters are as follows — {responseMessage}"); } @@ -132,7 +132,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.GetServiceFlags: { var responseMessage = billValidator.GetServiceFlags().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo($"The service flags are as follows — {responseMessage}"); } @@ -155,7 +155,7 @@ public override int Execute(CommandContext context, Settings settings) var correctableComponent = (CorrectableComponent)index; var responseMessage = billValidator.ClearServiceFlags(correctableComponent).Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo($"Successfully cleared the service flag for index {index}."); } @@ -170,7 +170,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.GetServiceInfo: { var responseMessage = billValidator.GetServiceInfo().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo($"The service info is as follows — {responseMessage}"); } @@ -185,7 +185,7 @@ public override int Execute(CommandContext context, Settings settings) case TelemetryCommand.GetFirmwareMetrics: { var responseMessage = billValidator.GetFirmwareMetrics().Result; - if (responseMessage is not null) + if (responseMessage.IsValid) { commandLogger.LogInfo($"The firmware metrics are as follows — {responseMessage}"); } diff --git a/PTI.Rs232Validator.Test/BillValidatorTests.cs b/PTI.Rs232Validator.Test/Tests/BillValidatorTests.cs similarity index 70% rename from PTI.Rs232Validator.Test/BillValidatorTests.cs rename to PTI.Rs232Validator.Test/Tests/BillValidatorTests.cs index a9aeacf..c0f77b6 100644 --- a/PTI.Rs232Validator.Test/BillValidatorTests.cs +++ b/PTI.Rs232Validator.Test/Tests/BillValidatorTests.cs @@ -1,19 +1,82 @@ using Moq; -using Moq.Language; using PTI.Rs232Validator.BillValidators; using PTI.Rs232Validator.Loggers; +using PTI.Rs232Validator.Messages; +using PTI.Rs232Validator.Messages.Commands; +using PTI.Rs232Validator.Messages.Requests; +using PTI.Rs232Validator.Messages.Responses.Extended; using PTI.Rs232Validator.SerialProviders; +using PTI.Rs232Validator.Test.Utility; using System; using System.Collections.Generic; +using System.Linq; using System.Text; using System.Threading.Tasks; -namespace PTI.Rs232Validator.Test; +namespace PTI.Rs232Validator.Test.Tests; public class BillValidatorTests { + [Test] + public async Task AllCommunicationAttemptsAreReported() + { + var validResponsePayload = new byte[] + { 0x02, 11, 0x21, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2, 0x03, 0x00 }; + validResponsePayload[^1] = Rs232Message.CalculateChecksum(validResponsePayload); + + var invalidResponsePayload = new byte[] + { 0x03, 11, 0x20, 0b00000000, 0b00000000, 0b00000000, 0, 1, 2, 0x03, 0x00 }; + invalidResponsePayload[^1] = Rs232Message.CalculateChecksum(invalidResponsePayload); + + var mockSerialProvider = new Mock(); + mockSerialProvider + .Setup(sp => sp.TryOpen()) + .Returns(true); + mockSerialProvider + .SetupSequence(sp => sp.Read(It.IsAny())) + .ReturnsValidPollResponses() + .ReturnsResponse(validResponsePayload) + .ReturnsResponse(invalidResponsePayload) + .ReturnsEmptyResponses(); + + var rs232Configuration = new Rs232Configuration + { + PollingPeriod = TimeSpan.Zero + }; + using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); + + var communicationAttempts = 0; + var wasValidResponseReported = false; + var wasInvalidResponseReported = false; + billValidator.OnCommunicationAttempted += (_, args) => + { + communicationAttempts++; + if (args.ResponseMessage.Payload.SequenceEqual(validResponsePayload)) + { + wasValidResponseReported = true; + } + else if (args.ResponseMessage.Payload.SequenceEqual(invalidResponsePayload)) + { + wasInvalidResponseReported = true; + } + }; + + var didPollingLoopStart = billValidator.StartPollingLoop(); + Assert.That(didPollingLoopStart, Is.True); + + while (billValidator.IsConnectionPresent) + { + await Task.Delay(rs232Configuration.PollingPeriod); + } + + Assert.That(communicationAttempts, + Is.EqualTo(BillValidator.SuccessfulPollsRequiredToStartPollingLoop + 2 + 1)); + Assert.That(wasValidResponseReported, Is.True); + Assert.That(wasInvalidResponseReported, Is.True); + } + [Test, TestCaseSource(nameof(ResponsePayloadAndStatePairs))] - public async Task ReportsNewStatesFromPollResponseMessages(byte[] responsePayload, Rs232State expectedState) + public async Task ANewStateEncodedInAPollResponseIsReported(byte[] responsePayload, Rs232State expectedState) { var mockSerialProvider = new Mock(); mockSerialProvider @@ -46,7 +109,8 @@ public async Task ReportsNewStatesFromPollResponseMessages(byte[] responsePayloa } [Test, TestCaseSource(nameof(ResponsePayloadAndEventPairs))] - public async Task ReportsSingleEventsFromPollResponseMessages(byte[] responsePayload, Rs232Event expectedEvent) + public async Task ASingleEventEncodedInAPollResponseMessageIsReported(byte[] responsePayload, + Rs232Event expectedEvent) { var mockSerialProvider = new Mock(); mockSerialProvider @@ -79,7 +143,7 @@ public async Task ReportsSingleEventsFromPollResponseMessages(byte[] responsePay } [Test] - public async Task ReportsMultipleEventsFromPollResponseMessages() + public async Task MultipleEventsEncodedInAPollResponseMessageAreReported() { var responsePayload = new byte[] { 0x02, 11, 0x21, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2, 0x03, 0x00 }; var expectedEvent = Rs232Event.None; @@ -90,10 +154,10 @@ public async Task ReportsMultipleEventsFromPollResponseMessages() { responsePayload[i] |= payload[i]; } - + expectedEvent |= (Rs232Event)((object[])obj)[1]; } - + var mockSerialProvider = new Mock(); mockSerialProvider .Setup(sp => sp.TryOpen()) @@ -103,32 +167,32 @@ public async Task ReportsMultipleEventsFromPollResponseMessages() .ReturnsValidPollResponses() .ReturnsResponse(responsePayload) .ReturnsEmptyResponses(); - + var rs232Configuration = new Rs232Configuration { PollingPeriod = TimeSpan.Zero }; using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); - + var actualEvent = Rs232Event.None; billValidator.OnEventReported += (_, e) => { actualEvent |= e; }; - + var didPollingLoopStart = billValidator.StartPollingLoop(); Assert.That(didPollingLoopStart, Is.True); - + while (billValidator.IsConnectionPresent) { await Task.Delay(rs232Configuration.PollingPeriod); } - + Assert.That(actualEvent, Is.EqualTo(expectedEvent)); } - + [Test] - public async Task ReportsCashboxAttachmentFromPollResponseMessages() + public async Task TheCashboxPresenceEncodedInAPollResponseMessageIsReported() { var responsePayload = new byte[] { 0x02, 11, 0x21, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2, 0x03, 0x00 }; - + var mockSerialProvider = new Mock(); mockSerialProvider .Setup(sp => sp.TryOpen()) @@ -138,32 +202,32 @@ public async Task ReportsCashboxAttachmentFromPollResponseMessages() .ReturnsValidPollResponses() .ReturnsResponse(responsePayload) .ReturnsEmptyResponses(); - + var rs232Configuration = new Rs232Configuration { PollingPeriod = TimeSpan.Zero }; using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); - + var wasCashboxAttached = false; billValidator.OnCashboxAttached += (_, _) => { wasCashboxAttached = true; }; - + var didPollingLoopStart = billValidator.StartPollingLoop(); Assert.That(didPollingLoopStart, Is.True); - + while (billValidator.IsConnectionPresent) { await Task.Delay(rs232Configuration.PollingPeriod); } - + Assert.That(wasCashboxAttached, Is.True); } - + [Test] - public async Task ReportsCashboxRemovalFromPollResponseMessages() + public async Task TheCashboxAbsenceEncodedInAPollResponseMessageIsReported() { var responsePayload = new byte[] { 0x02, 11, 0x21, 0b00000001, 0b00000000, 0b00000000, 0, 1, 2, 0x03, 0x00 }; - + var mockSerialProvider = new Mock(); mockSerialProvider .Setup(sp => sp.TryOpen()) @@ -173,29 +237,29 @@ public async Task ReportsCashboxRemovalFromPollResponseMessages() .ReturnsValidPollResponses() .ReturnsResponse(responsePayload) .ReturnsEmptyResponses(); - + var rs232Configuration = new Rs232Configuration { PollingPeriod = TimeSpan.Zero }; using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); - + var wasCashboxRemoved = false; billValidator.OnCashboxRemoved += (_, _) => { wasCashboxRemoved = true; }; - + var didPollingLoopStart = billValidator.StartPollingLoop(); Assert.That(didPollingLoopStart, Is.True); - + while (billValidator.IsConnectionPresent) { await Task.Delay(rs232Configuration.PollingPeriod); } - + Assert.That(wasCashboxRemoved, Is.True); } [Test, TestCaseSource(nameof(ResponsePayloadAndStackedBillPairs))] - public async Task ReportsStackedBillsFromPollResponseMessages(byte[] responsePayload, byte expectedBillType) + public async Task AStackedBillEncodedInAPollResponseMessageIsReported(byte[] responsePayload, byte expectedBillType) { var mockSerialProvider = new Mock(); mockSerialProvider @@ -206,29 +270,30 @@ public async Task ReportsStackedBillsFromPollResponseMessages(byte[] responsePay .ReturnsValidPollResponses() .ReturnsResponse(responsePayload) .ReturnsEmptyResponses(); - + var rs232Configuration = new Rs232Configuration { PollingPeriod = TimeSpan.Zero }; using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); - + var actualBillType = 0; billValidator.OnBillStacked += (_, billType) => { actualBillType = billType; }; - + var didPollingLoopStart = billValidator.StartPollingLoop(); Assert.That(didPollingLoopStart, Is.True); - + while (billValidator.IsConnectionPresent) { await Task.Delay(rs232Configuration.PollingPeriod); } - + Assert.That(actualBillType, Is.EqualTo(expectedBillType)); } - + [Test, TestCaseSource(nameof(ResponsePayloadAndEscrowedBillPairs))] - public async Task ReportsEscrowedBillsFromPollResponseMessages(byte[] responsePayload, byte expectedBillType) + public async Task AnEscrowedBillEncodedInAPollResponseMessageIsReported(byte[] responsePayload, + byte expectedBillType) { var mockSerialProvider = new Mock(); mockSerialProvider @@ -239,36 +304,36 @@ public async Task ReportsEscrowedBillsFromPollResponseMessages(byte[] responsePa .ReturnsValidPollResponses() .ReturnsResponse(responsePayload) .ReturnsEmptyResponses(); - + var rs232Configuration = new Rs232Configuration { PollingPeriod = TimeSpan.Zero }; using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); - + var actualBillType = 0; billValidator.OnBillEscrowed += (_, billType) => { actualBillType = billType; }; - + var didPollingLoopStart = billValidator.StartPollingLoop(); Assert.That(didPollingLoopStart, Is.True); - + while (billValidator.IsConnectionPresent) { await Task.Delay(rs232Configuration.PollingPeriod); } - + Assert.That(actualBillType, Is.EqualTo(expectedBillType)); } - + [Test] - public async Task ReportsDetectedBarcodesFromExtendedResponseMessages() + public async Task ADetectedBarcodeEncodedInAnExtendedResponseMessageIsReported() { const string expectedBarcode = "0123456789ABCDEFGHIJKLMNOPQR"; - + var responsePayload = new List { 0x02, 40, 0x71, 0x01, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2 }; responsePayload.AddRange(Encoding.ASCII.GetBytes(expectedBarcode)); responsePayload.AddRange([0x03, 0x00]); - + var mockSerialProvider = new Mock(); mockSerialProvider .Setup(sp => sp.TryOpen()) @@ -278,29 +343,29 @@ public async Task ReportsDetectedBarcodesFromExtendedResponseMessages() .ReturnsValidPollResponses() .ReturnsResponse(responsePayload.ToArray()) .ReturnsEmptyResponses(); - + var rs232Configuration = new Rs232Configuration { PollingPeriod = TimeSpan.Zero }; using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); - + var actualBarcode = string.Empty; billValidator.OnBarcodeDetected += (_, barcode) => { actualBarcode = barcode; }; - + var didPollingLoopStart = billValidator.StartPollingLoop(); Assert.That(didPollingLoopStart, Is.True); - + while (billValidator.IsConnectionPresent) { await Task.Delay(rs232Configuration.PollingPeriod); } - + Assert.That(actualBarcode, Is.EqualTo(expectedBarcode)); } - + [Test] - public async Task ReportsLostConnectionFromExtendedResponseMessages() + public async Task ALostConnectionIsReported() { var mockSerialProvider = new Mock(); mockSerialProvider @@ -310,27 +375,110 @@ public async Task ReportsLostConnectionFromExtendedResponseMessages() .SetupSequence(sp => sp.Read(It.IsAny())) .ReturnsValidPollResponses() .ReturnsEmptyResponses(); - + var rs232Configuration = new Rs232Configuration { PollingPeriod = TimeSpan.Zero }; using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); - + var wasConnectionLost = false; billValidator.OnConnectionLost += (_, _) => { wasConnectionLost = true; }; - + var didPollingLoopStart = billValidator.StartPollingLoop(); Assert.That(didPollingLoopStart, Is.True); - + while (billValidator.IsConnectionPresent) { await Task.Delay(rs232Configuration.PollingPeriod); } - + Assert.That(wasConnectionLost, Is.True); } + [Test] + public async Task ANonPollMessageCanBeSentOutsideAndDuringPolling() + { + const string expectedBarcode = "0123456789ABCDEFGHIJKLMNOPQR"; + + var barcodeResponsePayload = new List + { 0x02, 40, 0x71, 0x01, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2 }; + barcodeResponsePayload.AddRange(Encoding.ASCII.GetBytes(expectedBarcode)); + barcodeResponsePayload.AddRange([0x03, 0x00]); + + var mockSerialProvider = new Mock(); + mockSerialProvider + .Setup(sp => sp.TryOpen()) + .Returns(true); + mockSerialProvider + .SetupSequence(sp => sp.Read(It.IsAny())) + .ReturnsValidPollResponses() + .ReturnsResponse(barcodeResponsePayload.ToArray()); + + var rs232Configuration = new Rs232Configuration + { + PollingPeriod = TimeSpan.Zero + }; + using var billValidator = new BillValidator(new NullLogger(), mockSerialProvider.Object, rs232Configuration); + + var responseMessage1 = await billValidator.SendNonPollMessageAsync( + ack => new ExtendedRequestMessage(ack, ExtendedCommand.BarcodeDetected, []), + payload => new BarcodeDetectedResponseMessage(payload)); + + var wasBarcodeRequested = false; + var requestAck = false; + mockSerialProvider + .Setup(sp => sp.Write(It.IsAny())) + .Callback((byte[] payload) => + { + if (payload.Length < 2) + { + return; + } + + wasBarcodeRequested = (payload[2] & 0b11110000) == 0x70; + requestAck = (payload[2] & 0x01) == 1; + }); + + var responsePayloadRemainder = Array.Empty(); + mockSerialProvider + .Setup(sp => sp.Read(It.Is(count => count == 2))) + .Returns(() => + { + byte[] responsePayload; + if (wasBarcodeRequested) + { + responsePayload = barcodeResponsePayload.ToArray(); + responsePayload[2] = + requestAck ? (byte)(responsePayload[2] | 0x01) : (byte)(responsePayload[2] & ~0x01); + } + else + { + responsePayload = requestAck + ? MoqExtensions.OneAckValidPollResponsePayload + : MoqExtensions.ZeroAckValidPollResponsePayload; + } + + responsePayload[^1] = Rs232Message.CalculateChecksum(responsePayload); + responsePayloadRemainder = responsePayload[2..]; + return responsePayload[..2]; + }); + + mockSerialProvider + .Setup(sp => sp.Read(It.Is(count => count != 2))) + .Returns(() => responsePayloadRemainder); + + var didPollingLoopStart = billValidator.StartPollingLoop(); + Assert.That(didPollingLoopStart, Is.True); + + var responseMessage2 = await billValidator.SendNonPollMessageAsync( + ack => new ExtendedRequestMessage(ack, ExtendedCommand.BarcodeDetected, []), + payload => new BarcodeDetectedResponseMessage(payload)); + + Assert.That(responseMessage1.IsValid, Is.True); + Assert.That(responseMessage2.IsValid, Is.True); + } + private static readonly object[] ResponsePayloadAndStatePairs = [ new object[] @@ -447,7 +595,7 @@ public async Task ReportsLostConnectionFromExtendedResponseMessages() (byte)7 } ]; - + private static readonly object[] ResponsePayloadAndEscrowedBillPairs = [ new object[] @@ -486,57 +634,4 @@ public async Task ReportsLostConnectionFromExtendedResponseMessages() (byte)7 } ]; -} - -internal static class BillValidatorTestsExtensions -{ - private static readonly byte[] ValidPollResponsePayload1 = - [0x02, 11, 0x21, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2, 0x03, 0x00]; - - private static readonly byte[] ValidPollResponsePayload2 = - [0x02, 11, 0x20, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2, 0x03, 0x00]; - - public static ISetupSequentialResult ReturnsResponse(this ISetupSequentialResult result, - byte[] responsePayload) - { - var payloadCopy = new byte[responsePayload.Length]; - responsePayload.CopyTo(payloadCopy, 0); - payloadCopy[^1] = responsePayload.CalculateChecksum(); - - return result - .Returns(payloadCopy[..2]) - .Returns(payloadCopy[2..]); - } - - public static ISetupSequentialResult ReturnsValidPollResponses(this ISetupSequentialResult result) - { - for (var i = 0; i < BillValidator.SuccessfulPollsRequiredToStartPollingLoop; i++) - { - var payload = i % 2 == 0 ? ValidPollResponsePayload1 : ValidPollResponsePayload2; - result = result.ReturnsResponse(payload); - } - - return result; - } - - public static ISetupSequentialResult ReturnsEmptyResponses(this ISetupSequentialResult result) - { - for (var i = 0; i < BillValidator.MaxReadAttempts; i++) - { - result = result.Returns([]); - } - - return result; - } - - private static byte CalculateChecksum(this byte[] payload) - { - byte checksum = 0; - for (var i = 1; i < payload.Length - 2; ++i) - { - checksum ^= payload[i]; - } - - return checksum; - } } \ No newline at end of file diff --git a/PTI.Rs232Validator.Test/Utility/MoqExtensions.cs b/PTI.Rs232Validator.Test/Utility/MoqExtensions.cs new file mode 100644 index 0000000..8b74e4a --- /dev/null +++ b/PTI.Rs232Validator.Test/Utility/MoqExtensions.cs @@ -0,0 +1,47 @@ +using Moq.Language; +using PTI.Rs232Validator.BillValidators; +using PTI.Rs232Validator.Messages; + +namespace PTI.Rs232Validator.Test.Utility; + +public static class MoqExtensions +{ + public static readonly byte[] OneAckValidPollResponsePayload = + [0x02, 11, 0x21, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2, 0x03, 0x00]; + + public static readonly byte[] ZeroAckValidPollResponsePayload = + [0x02, 11, 0x20, 0b00000001, 0b00010000, 0b00000000, 0, 1, 2, 0x03, 0x00]; + + public static ISetupSequentialResult ReturnsResponse(this ISetupSequentialResult result, + byte[] responsePayload) + { + var payloadCopy = new byte[responsePayload.Length]; + responsePayload.CopyTo(payloadCopy, 0); + payloadCopy[^1] = Rs232Message.CalculateChecksum(responsePayload); + + return result + .Returns(payloadCopy[..2]) + .Returns(payloadCopy[2..]); + } + + public static ISetupSequentialResult ReturnsValidPollResponses(this ISetupSequentialResult result) + { + for (var i = 0; i < BillValidator.SuccessfulPollsRequiredToStartPollingLoop; i++) + { + var payload = i % 2 == 0 ? OneAckValidPollResponsePayload : ZeroAckValidPollResponsePayload; + result = result.ReturnsResponse(payload); + } + + return result; + } + + public static ISetupSequentialResult ReturnsEmptyResponses(this ISetupSequentialResult result) + { + for (var i = 0; i < BillValidator.MaxReadAttempts; i++) + { + result = result.Returns([]); + } + + return result; + } +} \ No newline at end of file diff --git a/PTI.Rs232Validator/BillValidators/BillValidator.Extended.cs b/PTI.Rs232Validator/BillValidators/BillValidator.Extended.cs index f33ecdf..f6e3d4e 100644 --- a/PTI.Rs232Validator/BillValidators/BillValidator.Extended.cs +++ b/PTI.Rs232Validator/BillValidators/BillValidator.Extended.cs @@ -12,15 +12,18 @@ public partial class BillValidator /// /// Gets the last detected barcode after a power cycle. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetDetectedBarcode() + public async Task GetDetectedBarcode() { return await SendExtendedMessageAsync(ExtendedCommand.BarcodeDetected, [], payload => new BarcodeDetectedResponseMessage(payload)); } - private async Task SendExtendedMessageAsync(ExtendedCommand command, + private async Task SendExtendedMessageAsync(ExtendedCommand command, IReadOnlyList requestData, Func, TResponseMessage> createResponseMessage) where TResponseMessage : ExtendedResponseMessage { diff --git a/PTI.Rs232Validator/BillValidators/BillValidator.Telemetry.cs b/PTI.Rs232Validator/BillValidators/BillValidator.Telemetry.cs index 3460cb4..18b1076 100644 --- a/PTI.Rs232Validator/BillValidators/BillValidator.Telemetry.cs +++ b/PTI.Rs232Validator/BillValidators/BillValidator.Telemetry.cs @@ -12,9 +12,12 @@ public partial class BillValidator /// /// Pings the acceptor. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task PingAsync() + public async Task PingAsync() { return await SendTelemetryMessageAsync(TelemetryCommand.Ping, [], payload => new TelemetryResponseMessage(payload)); @@ -23,9 +26,12 @@ public partial class BillValidator /// /// Gets the serial number assigned to the acceptor. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetSerialNumberAsync() + public async Task GetSerialNumberAsync() { return await SendTelemetryMessageAsync(TelemetryCommand.GetSerialNumber, [], payload => new GetSerialNumberResponseMessage(payload)); @@ -34,9 +40,12 @@ public partial class BillValidator /// /// Gets the telemetry metrics about the cashbox. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetCashboxMetrics() + public async Task GetCashboxMetrics() { return await SendTelemetryMessageAsync(TelemetryCommand.GetCashboxMetrics, [], payload => new GetCashboxMetricsResponseMessage(payload)); @@ -45,9 +54,12 @@ public partial class BillValidator /// /// Clears the count of bills in the cashbox. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task ClearCashboxCount() + public async Task ClearCashboxCount() { return await SendTelemetryMessageAsync(TelemetryCommand.ClearCashboxCount, [], payload => new TelemetryResponseMessage(payload)); @@ -56,9 +68,12 @@ public partial class BillValidator /// /// Gets the general telemetry metrics for an acceptor. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetUnitMetrics() + public async Task GetUnitMetrics() { return await SendTelemetryMessageAsync(TelemetryCommand.GetUnitMetrics, [], payload => new GetUnitMetricsResponseMessage(payload)); @@ -67,9 +82,12 @@ public partial class BillValidator /// /// Gets the telemetry metrics since the last time an acceptor was serviced. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetServiceUsageCounters() + public async Task GetServiceUsageCounters() { return await SendTelemetryMessageAsync(TelemetryCommand.GetServiceUsageCounters, [], payload => new GetServiceUsageCountersResponseMessage(payload)); @@ -78,9 +96,12 @@ public partial class BillValidator /// /// Gets the flags about what needs to be serviced. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetServiceFlags() + public async Task GetServiceFlags() { return await SendTelemetryMessageAsync(TelemetryCommand.GetServiceFlags, [], payload => new GetServiceFlagsResponseMessage(payload)); @@ -90,9 +111,12 @@ public partial class BillValidator /// Clears 1 or more service flags. /// /// The component to clear the service flag for. - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task ClearServiceFlags(CorrectableComponent correctableComponent) + public async Task ClearServiceFlags(CorrectableComponent correctableComponent) { return await SendTelemetryMessageAsync(TelemetryCommand.ClearServiceFlags, [(byte)correctableComponent], payload => new TelemetryResponseMessage(payload)); @@ -101,9 +125,12 @@ public partial class BillValidator /// /// Gets the info that was attached to the last service. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetServiceInfo() + public async Task GetServiceInfo() { return await SendTelemetryMessageAsync(TelemetryCommand.GetServiceInfo, [], payload => new GetServiceInfoResponseMessage(payload)); @@ -112,15 +139,18 @@ public partial class BillValidator /// /// Gets the telemetry metrics that pertain to an acceptor's firmware. /// - /// An instance of if successful; otherwise, null. + /// + /// An instance of with + /// set to true if successful. + /// /// The work is queued on the thread pool. - public async Task GetFirmwareMetrics() + public async Task GetFirmwareMetrics() { return await SendTelemetryMessageAsync(TelemetryCommand.GetFirmwareMetrics, [], payload => new GetFirmwareMetricsResponseMessage(payload)); } - private async Task SendTelemetryMessageAsync(TelemetryCommand command, + private async Task SendTelemetryMessageAsync(TelemetryCommand command, IReadOnlyList requestData, Func, TResponseMessage> createResponseMessage) where TResponseMessage : TelemetryResponseMessage { diff --git a/PTI.Rs232Validator/BillValidators/BillValidator.cs b/PTI.Rs232Validator/BillValidators/BillValidator.cs index 9622317..3991be2 100644 --- a/PTI.Rs232Validator/BillValidators/BillValidator.cs +++ b/PTI.Rs232Validator/BillValidators/BillValidator.cs @@ -75,7 +75,7 @@ public BillValidator(ILogger logger, ISerialProvider serialProvider, Rs232Config /// An event that is raised when the cashbox is attached. /// public event EventHandler? OnCashboxAttached; - + /// /// An event that is raised when the cashbox is removed. /// @@ -146,7 +146,7 @@ public bool StartPollingLoop() { _isPolling = true; } - + _worker = Task.Factory.StartNew(LoopPollMessages, TaskCreationOptions.LongRunning); IsConnectionPresent = true; return true; @@ -205,7 +205,6 @@ public void StackBill() } } - lock (_mutex) { _shouldRequestBillStack = true; @@ -232,6 +231,78 @@ public void ReturnBill() } } + /// + /// Sends an instance of , which should not be an instance of + /// , to the acceptor and returns an instance of + /// created from the response payload. + /// + internal async Task SendNonPollMessageAsync( + Func createRequestMessage, + Func, TResponseMessage> createResponseMessage) + where TResponseMessage : Rs232ResponseMessage + { + var eventWaitHandle = new ManualResetEvent(false); + var incorrectPayloadCount = 0; + TResponseMessage responseMessage = createResponseMessage([]); + var messageCallback = new Func(() => + { + var messageRetrievalResult = + TrySendMessage(createRequestMessage, createResponseMessage, out responseMessage); + switch (messageRetrievalResult) + { + case MessageRetrievalResult.IncorrectPayload when ++incorrectPayloadCount <= MaxIncorrectPayloadPardons: + case MessageRetrievalResult.IncorrectAck: + return false; + } + + if (messageRetrievalResult == MessageRetrievalResult.IncorrectPayload) + { + LogPayloadIssues(responseMessage); + } + + eventWaitHandle.Set(); + return true; + }); + + bool isPolling; + lock (_mutex) + { + isPolling = _isPolling; + } + + if (isPolling) + { + EnqueueMessageCallback(messageCallback); + return await Task.Run(() => + { + eventWaitHandle.WaitOne(); + return responseMessage; + }); + } + + return await Task.Run(() => + { + if (!TryOpenPort()) + { + return responseMessage; + } + + if (!CheckForDevice()) + { + ClosePort(); + return responseMessage; + } + + while (!messageCallback.Invoke()) + { + Thread.Sleep(Configuration.PollingPeriod); + } + + ClosePort(); + return responseMessage; + }); + } + private void EnqueueMessageCallback(Func messageCallback) { lock (_mutex) @@ -493,75 +564,6 @@ private bool TrySendPollMessage(Func createPollRequest return true; } - private async Task SendNonPollMessageAsync( - Func createRequestMessage, - Func, TResponseMessage> createResponseMessage) - where TResponseMessage : Rs232ResponseMessage - { - var eventWaitHandle = new ManualResetEvent(false); - var incorrectPayloadCount = 0; - TResponseMessage? responseMessage = null; - var messageCallback = new Func(() => - { - var messageRetrievalResult = TrySendMessage(createRequestMessage, createResponseMessage, out var r); - switch (messageRetrievalResult) - { - case MessageRetrievalResult.IncorrectPayload when ++incorrectPayloadCount <= MaxIncorrectPayloadPardons: - case MessageRetrievalResult.IncorrectAck: - return false; - case MessageRetrievalResult.Success: - responseMessage = r; - break; - } - - if (messageRetrievalResult == MessageRetrievalResult.IncorrectPayload) - { - LogPayloadIssues(r); - } - - eventWaitHandle.Set(); - return true; - }); - - bool isRunning; - lock (_mutex) - { - isRunning = _isPolling; - } - - if (isRunning) - { - EnqueueMessageCallback(messageCallback); - return await Task.Run(() => - { - eventWaitHandle.WaitOne(); - return responseMessage; - }); - } - - return await Task.Run(() => - { - if (!TryOpenPort()) - { - return null; - } - - if (!CheckForDevice()) - { - ClosePort(); - return null; - } - - while (!messageCallback.Invoke()) - { - Thread.Sleep(Configuration.PollingPeriod); - } - - ClosePort(); - return responseMessage; - }); - } - private bool CheckForDevice() { var successfulPolls = 0; diff --git a/PTI.Rs232Validator/Messages/Rs232Message.cs b/PTI.Rs232Validator/Messages/Rs232Message.cs index a47dd75..4327f98 100644 --- a/PTI.Rs232Validator/Messages/Rs232Message.cs +++ b/PTI.Rs232Validator/Messages/Rs232Message.cs @@ -46,7 +46,7 @@ public abstract class Rs232Message /// /// The payload to calculate the checksum of. /// The checksum. - protected static byte CalculateChecksum(IReadOnlyList payload) + internal static byte CalculateChecksum(IReadOnlyList payload) { if (payload.Count < MinPayloadByteSize) { diff --git a/PTI.Rs232Validtor.Desktop/Views/MainWindow.Extended.cs b/PTI.Rs232Validtor.Desktop/Views/MainWindow.Extended.cs index d41f1fe..3d0a1ef 100644 --- a/PTI.Rs232Validtor.Desktop/Views/MainWindow.Extended.cs +++ b/PTI.Rs232Validtor.Desktop/Views/MainWindow.Extended.cs @@ -41,11 +41,11 @@ private async void GetDetectedBarcodeDisplay_OnClickAsync(object sender, RoutedE var responseMessage = await billValidator.GetDetectedBarcode(); string resultValue; - if (!string.IsNullOrEmpty(responseMessage?.Barcode)) + if (responseMessage is { IsValid: true, Barcode.Length: > 0 }) { resultValue = responseMessage.Barcode; } - else if (responseMessage is not null) + else if (responseMessage is { IsValid: true, Barcode.Length: 0 }) { resultValue = "No barcode was detected since the last power cycle."; } diff --git a/PTI.Rs232Validtor.Desktop/Views/MainWindow.Telemetry.cs b/PTI.Rs232Validtor.Desktop/Views/MainWindow.Telemetry.cs index 496c7ee..5986885 100644 --- a/PTI.Rs232Validtor.Desktop/Views/MainWindow.Telemetry.cs +++ b/PTI.Rs232Validtor.Desktop/Views/MainWindow.Telemetry.cs @@ -14,7 +14,7 @@ private async void PingDisplay_OnClickAsync(object sender, RoutedEventArgs e) return; } - var wasSuccessful = await billValidator.PingAsync() is not null; + var wasSuccessful = (await billValidator.PingAsync()).IsValid; DoOnUiThread(() => PingDisplay.ResultValue = wasSuccessful.ToString()); } @@ -28,11 +28,11 @@ private async void GetSerialNumberDisplay_OnClickAsync(object sender, RoutedEven var responseMessage = await billValidator.GetSerialNumberAsync(); string resultValue; - if (!string.IsNullOrEmpty(responseMessage?.SerialNumber)) + if (responseMessage is { IsValid: true, SerialNumber.Length: > 0 }) { resultValue = responseMessage.SerialNumber; } - else if (responseMessage is not null) + else if (responseMessage is { IsValid: true, SerialNumber.Length: 0 }) { resultValue = "The acceptor was not assigned a serial number."; } @@ -53,7 +53,7 @@ private async void GetCashboxMetricsDisplay_OnClickAsync(object sender, RoutedEv } var responseMessage = await billValidator.GetCashboxMetrics(); - var resultValue = responseMessage is null ? ErrorMessage : responseMessage.ToString(); + var resultValue = responseMessage.IsValid ? responseMessage.ToString() : ErrorMessage; DoOnUiThread(() => GetCashboxMetricsDisplay.ResultValue = resultValue); } @@ -65,7 +65,7 @@ private async void ClearCashboxCountDisplay_OnClickAsync(object sender, RoutedEv return; } - var wasSuccessful = await billValidator.ClearCashboxCount() is not null; + var wasSuccessful = (await billValidator.ClearCashboxCount()).IsValid; DoOnUiThread(() => ClearCashboxCountDisplay.ResultValue = wasSuccessful.ToString()); } @@ -78,7 +78,7 @@ private async void GetUnitMetricsDisplay_OnClickAsync(object sender, RoutedEvent } var responseMessage = await billValidator.GetUnitMetrics(); - var resultValue = responseMessage is null ? ErrorMessage : responseMessage.ToString(); + var resultValue = responseMessage.IsValid ? responseMessage.ToString() : ErrorMessage; DoOnUiThread(() => GetUnitMetricsDisplay.ResultValue = resultValue); } @@ -91,7 +91,7 @@ private async void GetServiceUsageCountersDisplay_OnClickAsync(object sender, Ro } var responseMessage = await billValidator.GetServiceUsageCounters(); - var resultValue = responseMessage is null ? ErrorMessage : responseMessage.ToString(); + var resultValue = responseMessage.IsValid ? responseMessage.ToString() : ErrorMessage; DoOnUiThread(() => GetServiceUsageCountersDisplay.ResultValue = resultValue); } @@ -104,7 +104,7 @@ private async void GetServiceFlagsDisplay_OnClickAsync(object sender, RoutedEven } var responseMessage = await billValidator.GetServiceFlags(); - var resultValue = responseMessage is null ? ErrorMessage : responseMessage.ToString(); + var resultValue = responseMessage.IsValid ? responseMessage.ToString() : ErrorMessage; DoOnUiThread(() => GetServiceFlagsDisplay.ResultValue = resultValue); } @@ -122,7 +122,7 @@ private async void ClearServiceFlagsDisplay_OnClickAsync(object sender, RoutedEv return; } - var wasSuccessful = await billValidator.ClearServiceFlags(correctableComponent) is not null; + var wasSuccessful = (await billValidator.ClearServiceFlags(correctableComponent)).IsValid; DoOnUiThread(() => ClearServiceFlagsDisplay.ResultValue = wasSuccessful.ToString()); } @@ -135,7 +135,7 @@ private async void GetServiceInfoDisplay_OnClickAsync(object sender, RoutedEvent } var responseMessage = await billValidator.GetServiceInfo(); - var resultValue = responseMessage is null ? ErrorMessage : responseMessage.ToString(); + var resultValue = responseMessage.IsValid ? responseMessage.ToString() : ErrorMessage; DoOnUiThread(() => GetServiceInfoDisplay.ResultValue = resultValue); } @@ -148,7 +148,7 @@ private async void GetFirmwareMetricsDisplay_OnClickAsync(object sender, RoutedE } var responseMessage = await billValidator.GetFirmwareMetrics(); - var resultValue = responseMessage is null ? ErrorMessage : responseMessage.ToString(); + var resultValue = responseMessage.IsValid ? responseMessage.ToString() : ErrorMessage; DoOnUiThread(() => GetFirmwareMetricsDisplay.ResultValue = resultValue); } } \ No newline at end of file