From 9b9d47488b363c629cd6645ff1cbad50c1b38d6e Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Wed, 14 Aug 2024 16:20:07 -0400 Subject: [PATCH 01/11] Added BSM objects --- JMayer.Example.WindowsService/BSM/BSM.cs | 185 ++++++++++++++++++ JMayer.Example.WindowsService/BSM/BSMPDU.cs | 28 +++ .../BSM/BSMParser.cs | 49 +++++ .../BSM/BaggageTagDetails.cs | 62 ++++++ JMayer.Example.WindowsService/BSM/ITypeB.cs | 19 ++ .../BSM/OutboundFlight.cs | 107 ++++++++++ .../BSM/PassengerName.cs | 80 ++++++++ .../BSM/VersionSupplementaryData.cs | 82 ++++++++ 8 files changed, 612 insertions(+) create mode 100644 JMayer.Example.WindowsService/BSM/BSM.cs create mode 100644 JMayer.Example.WindowsService/BSM/BSMPDU.cs create mode 100644 JMayer.Example.WindowsService/BSM/BSMParser.cs create mode 100644 JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs create mode 100644 JMayer.Example.WindowsService/BSM/ITypeB.cs create mode 100644 JMayer.Example.WindowsService/BSM/OutboundFlight.cs create mode 100644 JMayer.Example.WindowsService/BSM/PassengerName.cs create mode 100644 JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs diff --git a/JMayer.Example.WindowsService/BSM/BSM.cs b/JMayer.Example.WindowsService/BSM/BSM.cs new file mode 100644 index 0000000..eedcd96 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/BSM.cs @@ -0,0 +1,185 @@ +using System.ComponentModel.DataAnnotations; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class represents a simplified version of a baggage source message. +/// +/// +/// A baggage source message contains the passenger information, outbound flight information, +/// the baggage checked in at the ticket counter and other data. The outbound system uses this +/// to bind a bag scanned at a scanner to a flight and to an end point in the system. +/// +public class BSM : ITypeB +{ + /// + /// The constant for the Add change of status. + /// + public const string Add = "ADD"; + + /// + /// The property gets the baggage tag details. + /// + public BaggageTagDetails? BaggageTagDetails { get; private set; } + + /// + /// The constant for the change change of status. + /// + public const string Change = "CHG"; + + /// + /// The property gets the change of status. + /// + [Required] + public string ChangeOfStatus { get; private set; } = string.Empty; + + /// + /// The constant for the delete change of status. + /// + public const string Delete = "DEL"; + + /// + /// The constant for the end of BSM. + /// + public const string EndOfBSM = "ENDBSM"; + + /// + /// The property gets the outbound flight information. + /// + public OutboundFlight? OutboundFlight { get; private set; } + + /// + /// The property gets the passenger name. + /// + public PassengerName? PassengerName { get; private set; } + + /// + /// The property gets when the BSM was received. + /// + public DateTime ReceivedOn { get; init; } = DateTime.Now; + + /// + /// The constant for the start of BSM. + /// + public const string StartOfBSM = "BSM"; + + /// + /// The property gets the version supplementary data. + /// + public VersionSupplementaryData? VersionSupplementaryData { get; private set; } + + /// + /// The method returns the change of status from the BSM. + /// + /// The BSM to examine. + /// The change of status. + private static string GetChangeOfStatus(string bsm) + { + int index = bsm.IndexOf('.'); + + if (index > -1) + { + string header = bsm.Substring(0, index); + + if (header.Contains(Change)) + { + return Change; + } + else if (header.Contains(Delete)) + { + return Delete; + } + else + { + return Add; + } + } + + return string.Empty; + } + + /// + public void Parse(string typeBString) + { + ChangeOfStatus = GetChangeOfStatus(typeBString); + + //Remove the start & end identifiers because they're no longer needed. + typeBString = typeBString.Replace(StartOfBSM, string.Empty); + typeBString = typeBString.Replace(EndOfBSM, string.Empty); + + int totalBytesProcessed = 0; + + do + { + int startIndex = typeBString.IndexOf('.'); + + //Dot was not found so exit. + if (startIndex > -1) + { + break; + } + + int endIndex = typeBString.IndexOf('.', startIndex + 1); + + //If the next dot is not found then assume this is the last line. + if (endIndex == -1) + { + endIndex = typeBString.Length; + } + + string line = typeBString.Substring(startIndex, endIndex - startIndex); + + if (line.StartsWith(OutboundFlight.DotFElement)) + { + OutboundFlight = new OutboundFlight(); + OutboundFlight.Parse(line); + } + else if (line.StartsWith(BaggageTagDetails.DotNElement)) + { + BaggageTagDetails = new BaggageTagDetails(); + BaggageTagDetails.Parse(line); + } + else if (line.StartsWith(PassengerName.DotPElement)) + { + PassengerName = new PassengerName(); + PassengerName.Parse(line); + } + else if (line.StartsWith(VersionSupplementaryData.DotVElement)) + { + VersionSupplementaryData = new VersionSupplementaryData(); + VersionSupplementaryData.Parse(line); + } + + totalBytesProcessed += line.Length; + + } while (totalBytesProcessed < typeBString.Length); + } + + /// + public string ToTypeB() + { + string dotElements = string.Empty; + + if (OutboundFlight != null) + { + dotElements += OutboundFlight.ToTypeB(); + } + + if (BaggageTagDetails != null) + { + dotElements += BaggageTagDetails.ToTypeB(); + } + + if (PassengerName != null) + { + dotElements += PassengerName.ToTypeB(); + } + + if (VersionSupplementaryData != null) + { + dotElements += VersionSupplementaryData.ToTypeB(); + } + + return $"{StartOfBSM}{Environment.NewLine}{ChangeOfStatus}{Environment.NewLine}{dotElements}{EndOfBSM}{Environment.NewLine}"; + } +} diff --git a/JMayer.Example.WindowsService/BSM/BSMPDU.cs b/JMayer.Example.WindowsService/BSM/BSMPDU.cs new file mode 100644 index 0000000..6a80730 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/BSMPDU.cs @@ -0,0 +1,28 @@ +using JMayer.Net.ProtocolDataUnit; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class represents a BSM PDU. +/// +public class BSMPDU : PDU +{ + /// + /// The property gets/sets the BSM string. + /// + public BSM BSM { get; init; } = new(); + + /// + public override byte[] ToBytes() + { + return Encoding.ASCII.GetBytes(BSM.ToTypeB()); + } + + /// + public override List Validate() + { + return ValidateDataAnnotations(); + } +} diff --git a/JMayer.Example.WindowsService/BSM/BSMParser.cs b/JMayer.Example.WindowsService/BSM/BSMParser.cs new file mode 100644 index 0000000..7490b5f --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/BSMParser.cs @@ -0,0 +1,49 @@ +using JMayer.Net.ProtocolDataUnit; +using System.Text; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages parsing the BSM. +/// +public class BSMParser : PDUParser +{ + /// + protected override PDUParserResult SubClassParse(byte[] bytes) + { + int totalBytesProcessed = 0; + List pdus = []; + + string bytesAsString = Encoding.ASCII.GetString(bytes); + + do + { + int startIndex = bytesAsString.IndexOf(BSM.StartOfBSM, totalBytesProcessed); + + //Start was not found so exit. + if (startIndex == -1) + { + break; + } + + int endIndex = bytesAsString.IndexOf(BSM.EndOfBSM, startIndex); + + //End was not found or start is actual end, exit. + if (endIndex == -1 || startIndex == endIndex) + { + break; + } + + string bsmString = bytesAsString.Substring(startIndex, endIndex - startIndex); + + BSMPDU pdu = new(); + pdu.BSM.Parse(bsmString); + + pdus.Add(pdu); + totalBytesProcessed += endIndex - startIndex; + + } while (totalBytesProcessed < bytes.Length); + + return new PDUParserResult(pdus, totalBytesProcessed); + } +} diff --git a/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs b/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs new file mode 100644 index 0000000..a21646b --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs @@ -0,0 +1,62 @@ +using System.ComponentModel.DataAnnotations; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class represents the baggage tag details in the BSM. +/// +public class BaggageTagDetails : ITypeB +{ + /// + /// The property gets the number of baggage tag numbers. + /// + public int Count + { + get => BaggageTagNumbers.Count; + } + + /// + /// The property gets a list of baggage tag numbers. + /// + /// + [Length(1, 999)] + public List BaggageTagNumbers { get; init; } = []; + + /// + /// The constant for the .N element. + /// + public const string DotNElement = ".N"; + + /// + public void Parse(string typeBString) + { + //Remove the identifier and new line. + typeBString = typeBString.Replace($"{DotNElement}/", string.Empty); + typeBString = typeBString.Replace(Environment.NewLine, string.Empty); + + if (typeBString.Length is 13) + { + if (long.TryParse(typeBString.AsSpan(0, 10), out long iataNumber) && int.TryParse(typeBString.AsSpan(10, 3), out int length)) + { + for (int index = 0; index < length; index++) + { + string iataString = (iataNumber + index).ToString().PadLeft(10, '0'); + BaggageTagNumbers.Add(iataString); + } + } + } + } + + /// + public string ToTypeB() + { + if (Count == 0) + { + return string.Empty; + } + else + { + return $"{DotNElement}/{BaggageTagNumbers[0]}{Count:D3}{Environment.NewLine}"; + } + } +} diff --git a/JMayer.Example.WindowsService/BSM/ITypeB.cs b/JMayer.Example.WindowsService/BSM/ITypeB.cs new file mode 100644 index 0000000..aa235b9 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/ITypeB.cs @@ -0,0 +1,19 @@ +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The interface contains common methods for the type B string format. +/// +public interface ITypeB +{ + /// + /// The method parses a type B string. + /// + /// The type B string to parse. + void Parse(string typeBString); + + /// + /// The method returns the object in the type B string format. + /// + /// A string in the type B format. + string ToTypeB(); +} diff --git a/JMayer.Example.WindowsService/BSM/OutboundFlight.cs b/JMayer.Example.WindowsService/BSM/OutboundFlight.cs new file mode 100644 index 0000000..e78d879 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/OutboundFlight.cs @@ -0,0 +1,107 @@ +using System.ComponentModel.DataAnnotations; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class represents the outbound flight information in the BSM. +/// +public class OutboundFlight : ITypeB +{ + /// + /// The property gets the airline for the flight. + /// + [Required] + [RegularExpression("^[A-Z0-9]{2}$", ErrorMessage = "The airline must be 2 alphanumeric characters.")] + public string Airline { get; private set; } = string.Empty; + + /// + /// The property gets the class of travel for passenger for this flight. + /// + [RegularExpression("^([A-Z]{1})?$", ErrorMessage = "The class of travel must be 1 capital letter or empty.")] + public string ClassOfTravel { get; private set; } = string.Empty; + + /// + /// The property gets the destination for this flight. + /// + [RegularExpression("^([A-Z]{3})?$", ErrorMessage = "The destination must be 3 letters or empty.")] + public string Destination { get; private set; } = string.Empty; + + /// + /// The constant for the .F element. + /// + public const string DotFElement = ".F"; + + /// + /// The property gets the date this flight flies. + /// + /// + /// This will be formatted like JAN01. + /// + [Required] + [RegularExpression("^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)[0-9]{2}$", ErrorMessage = "The flight date must be the first three letters of the month, capitalized, followed by the day of month, 2 digits.")] + public string FlightDate { get; private set; } = string.Empty; + + /// + /// The property gets the identifier for the flight. + /// + [Required] + [RegularExpression("^([0-9]{4})|([0-9]{4}[A-Z]{1})$", ErrorMessage = "The flight number must be 4 digits and optionally a capital letter.")] + public string FlightNumber { get; private set; } = string.Empty; + + /// + public void Parse(string typeBString) + { + //Remove the identifier so the elements can be broken apart with Split(). + typeBString = typeBString.Replace($"{DotFElement}/", string.Empty); + typeBString = typeBString.Replace(Environment.NewLine, string.Empty); + + string[] elements = typeBString.Split('/'); + + //Handle parsing the airline and flight number. + if (elements.Length > 0 && !string.IsNullOrEmpty(elements[0])) + { + string airlineAndFlight = elements[0]; + + if (airlineAndFlight.Length >= 6) + { + Airline = airlineAndFlight.Substring(0, 2); + FlightNumber = airlineAndFlight.Substring(2, airlineAndFlight.Length - 2); + } + } + + //Handle parsing the flight date. + if (elements.Length > 1 && !string.IsNullOrEmpty(elements[1])) + { + FlightDate = elements[1]; + } + + //Handle parsing the destination. + if (elements.Length > 2 && !string.IsNullOrEmpty(elements[2])) + { + Destination = elements[2]; + } + + //Handle parsing the class of travel. + if (elements.Length > 3 && !string.IsNullOrEmpty(elements[3])) + { + ClassOfTravel = elements[3]; + } + } + + /// + public string ToTypeB() + { + if (!string.IsNullOrEmpty(ClassOfTravel)) + { + return $"{DotFElement}/{Airline}{FlightNumber}/{FlightDate}/{Destination}/{ClassOfTravel}{Environment.NewLine}"; + } + else if (!string.IsNullOrEmpty(Destination)) + { + return $"{DotFElement}/{Airline}{FlightNumber}/{FlightDate}/{Destination}{Environment.NewLine}"; + } + else + { + return $"{DotFElement}/{Airline}{FlightNumber}/{FlightDate}{Environment.NewLine}"; + } + } +} diff --git a/JMayer.Example.WindowsService/BSM/PassengerName.cs b/JMayer.Example.WindowsService/BSM/PassengerName.cs new file mode 100644 index 0000000..dd27170 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/PassengerName.cs @@ -0,0 +1,80 @@ +using System.ComponentModel.DataAnnotations; +using System.Text.RegularExpressions; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class represents the passenger name in the BSM. +/// +public class PassengerName : ITypeB +{ + /// + /// The constant for the .P element. + /// + public const string DotPElement = ".P"; + + /// + /// The property gets/sets the given names for the passenger. + /// + public List GivenNames { get; init; } = []; + + /// + /// The property gets/sets the surnaame for the passenger. + /// + [Required] + public string SurName { get; set; } = string.Empty; + + /// + public void Parse(string typeBString) + { + //Remove the identifier so the elements can be broken apart with Split(). + typeBString = typeBString.Replace($"{DotPElement}/", string.Empty); + typeBString = typeBString.Replace(Environment.NewLine , string.Empty); + + string[] elements = typeBString.Split('/'); + + //Handle parsing the surname. + if (elements.Length > 0 && !string.IsNullOrEmpty(elements[0])) + { + //The number of given names can be infront of the surname as either a 1 or 2 digit number + //so remove the number if it exists. + if (Regex.IsMatch(elements[0].AsSpan(0, 2), "^\\d$")) + { + SurName = elements[0].Substring(2); + } + else if (Regex.IsMatch(elements[0].AsSpan(0, 1), "^\\d$")) + { + SurName = elements[0].Substring(1); + } + else + { + SurName = elements[0]; + } + } + + //The rest of the elements will be the given names so add them to the list. + if (elements.Length > 1) + { + for (int index = 1; index < elements.Length; index++) + { + if (!string.IsNullOrEmpty(elements[index])) + { + GivenNames.Add(elements[index]); + } + } + } + } + + /// + public string ToTypeB() + { + string givenNames = string.Empty; + + foreach (string givenName in GivenNames) + { + givenNames += $"/{givenName}"; + } + + return $"{DotPElement}/{GivenNames.Count}{SurName}{givenNames}"; + } +} diff --git a/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs b/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs new file mode 100644 index 0000000..c761d6d --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs @@ -0,0 +1,82 @@ +using System.ComponentModel.DataAnnotations; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class represents the version & supplementary data in the BSM. +/// +public class VersionSupplementaryData : ITypeB +{ + /// + /// The property gets the aiport who sent the BSM. + /// + [Required] + [RegularExpression("^([A-Z]{3})?$", ErrorMessage = "The airport code must be 3 letters or empty.")] + public string AirportCode { get; private set; } = string.Empty; + + /// + /// The property gets the baggage source indicator (local, transfer, remote or terminating). + /// + /// + [Required] + [RegularExpression("^(L|R|X|T)$", ErrorMessage = "The baggage source indicator must be L, R, X or T.")] + public string BaggageSourceIndicator { get; private set; } = string.Empty; + + /// + /// The property gets the version number for the data dictionary. + /// + [Required] + public int DataDictionaryVersionNumber { get; private set; } + + /// + /// The constant for the .V element. + /// + public const string DotVElement = ".V"; + + /// + /// The constant for the local baggage source indicator. + /// + public const string LocalBaggageSourceIndicator = "L"; + + /// + /// The constant for the remote baggage source indicator. + /// + public const string RemoteBaggageSourceIndicator = "R"; + + /// + /// The constant for the terminating baggage source indicator. + /// + public const string TerminatingBaggageSourceIndicator = "X"; + + /// + /// The constant for the transfer baggage source indicator. + /// + public const string TransferBaggageSourceIndicator = "T"; + + /// + public void Parse(string typeBString) + { + //Remove the identifier so the elements can be broken apart with Split(). + typeBString = typeBString.Replace($"{DotVElement}/", string.Empty); + typeBString = typeBString.Replace(Environment.NewLine, string.Empty); + + string[] elements = typeBString.Split('/'); + + if (elements.Length > 0 && elements[0].Length is 5) + { + if (int.TryParse(elements[0].AsSpan(0, 1), out int dataDictionaryVersionNumber)) + { + DataDictionaryVersionNumber = dataDictionaryVersionNumber; + } + + BaggageSourceIndicator = elements[0].Substring(1, 1); + AirportCode = elements[0].Substring(2, 3); + } + } + + /// + public string ToTypeB() + { + return $"{DotVElement}/{DataDictionaryVersionNumber}{BaggageSourceIndicator}{AirportCode}{Environment.NewLine}"; + } +} From 13d1d4b6b2912d61c584713a2a217e55c0a03231 Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:07:38 -0400 Subject: [PATCH 02/11] Various fixes; initial parser unit test --- JMayer.Example.WindowsService.sln | 8 +- .../BSM/BMSEqualityComparer.cs | 35 ++ JMayer.Example.WindowsService/BSM/BSM.cs | 57 +- JMayer.Example.WindowsService/BSM/BSMPDU.cs | 24 +- .../BSM/BSMParser.cs | 15 +- .../BSM/BaggageTagDetailEqualityComparer.cs | 47 ++ .../BSM/BaggageTagDetails.cs | 1 - .../BSM/DateTimeExtensions.cs | 15 + .../BSM/OutboundFlight.cs | 14 +- .../BSM/OutboundFlightEqualityComparer.cs | 36 ++ .../BSM/PassengerNameEqualityComparer.cs | 47 ++ .../BSM/VersionSupplementaryData.cs | 6 +- ...ersionSupplementaryDataEqualityComparer.cs | 34 + TestProject/Test/BSMParserUnitTest.cs | 594 ++++++++++++++++++ TestProject/TestProject.csproj | 27 + 15 files changed, 918 insertions(+), 42 deletions(-) create mode 100644 JMayer.Example.WindowsService/BSM/BMSEqualityComparer.cs create mode 100644 JMayer.Example.WindowsService/BSM/BaggageTagDetailEqualityComparer.cs create mode 100644 JMayer.Example.WindowsService/BSM/DateTimeExtensions.cs create mode 100644 JMayer.Example.WindowsService/BSM/OutboundFlightEqualityComparer.cs create mode 100644 JMayer.Example.WindowsService/BSM/PassengerNameEqualityComparer.cs create mode 100644 JMayer.Example.WindowsService/BSM/VersionSupplementaryDataEqualityComparer.cs create mode 100644 TestProject/Test/BSMParserUnitTest.cs create mode 100644 TestProject/TestProject.csproj diff --git a/JMayer.Example.WindowsService.sln b/JMayer.Example.WindowsService.sln index 27d2a9d..4d39097 100644 --- a/JMayer.Example.WindowsService.sln +++ b/JMayer.Example.WindowsService.sln @@ -3,7 +3,9 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.11.35208.52 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JMayer.Example.WindowsService", "JMayer.Example.WindowsService\JMayer.Example.WindowsService.csproj", "{0BC4DB3A-032F-43BA-BC39-380442F0BED0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JMayer.Example.WindowsService", "JMayer.Example.WindowsService\JMayer.Example.WindowsService.csproj", "{0BC4DB3A-032F-43BA-BC39-380442F0BED0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestProject", "TestProject\TestProject.csproj", "{69EB99BC-2876-4339-AAFE-2AB2B406C0BA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +17,10 @@ Global {0BC4DB3A-032F-43BA-BC39-380442F0BED0}.Debug|Any CPU.Build.0 = Debug|Any CPU {0BC4DB3A-032F-43BA-BC39-380442F0BED0}.Release|Any CPU.ActiveCfg = Release|Any CPU {0BC4DB3A-032F-43BA-BC39-380442F0BED0}.Release|Any CPU.Build.0 = Release|Any CPU + {69EB99BC-2876-4339-AAFE-2AB2B406C0BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {69EB99BC-2876-4339-AAFE-2AB2B406C0BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {69EB99BC-2876-4339-AAFE-2AB2B406C0BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {69EB99BC-2876-4339-AAFE-2AB2B406C0BA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/JMayer.Example.WindowsService/BSM/BMSEqualityComparer.cs b/JMayer.Example.WindowsService/BSM/BMSEqualityComparer.cs new file mode 100644 index 0000000..64a6e81 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/BMSEqualityComparer.cs @@ -0,0 +1,35 @@ +using System.Diagnostics.CodeAnalysis; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages comparing two BSM objects. +/// +public class BMSEqualityComparer : IEqualityComparer +{ + /// + public bool Equals(BSM? x, BSM? y) + { + if (x == null || y == null) + { + return false; + } + + BaggageTagDetailEqualityComparer baggageTagDetailEqualityComparer = new(); + OutboundFlightEqualityComparer outboundFlightEqualityComparer = new(); + PassengerNameEqualityComparer passengerNameEqualityComparer = new(); + VersionSupplementaryDataEqualityComparer versionSupplementaryDataEqualityComparer = new(); + + return baggageTagDetailEqualityComparer.Equals(x.BaggageTagDetails, y.BaggageTagDetails) + && x.ChangeOfStatus == y.ChangeOfStatus + && outboundFlightEqualityComparer.Equals(x.OutboundFlight, y.OutboundFlight) + && passengerNameEqualityComparer.Equals(x.PassengerName, y.PassengerName) + && versionSupplementaryDataEqualityComparer.Equals(x.VersionSupplementaryData, y.VersionSupplementaryData); + } + + /// + public int GetHashCode([DisallowNull] BSM obj) + { + throw new NotImplementedException(); + } +} diff --git a/JMayer.Example.WindowsService/BSM/BSM.cs b/JMayer.Example.WindowsService/BSM/BSM.cs index eedcd96..b7fa8b5 100644 --- a/JMayer.Example.WindowsService/BSM/BSM.cs +++ b/JMayer.Example.WindowsService/BSM/BSM.cs @@ -20,7 +20,7 @@ public class BSM : ITypeB /// /// The property gets the baggage tag details. /// - public BaggageTagDetails? BaggageTagDetails { get; private set; } + public BaggageTagDetails? BaggageTagDetails { get; set; } /// /// The constant for the change change of status. @@ -31,7 +31,7 @@ public class BSM : ITypeB /// The property gets the change of status. /// [Required] - public string ChangeOfStatus { get; private set; } = string.Empty; + public string ChangeOfStatus { get; set; } = string.Empty; /// /// The constant for the delete change of status. @@ -46,12 +46,12 @@ public class BSM : ITypeB /// /// The property gets the outbound flight information. /// - public OutboundFlight? OutboundFlight { get; private set; } + public OutboundFlight? OutboundFlight { get; set; } /// /// The property gets the passenger name. /// - public PassengerName? PassengerName { get; private set; } + public PassengerName? PassengerName { get; set; } /// /// The property gets when the BSM was received. @@ -66,7 +66,7 @@ public class BSM : ITypeB /// /// The property gets the version supplementary data. /// - public VersionSupplementaryData? VersionSupplementaryData { get; private set; } + public VersionSupplementaryData? VersionSupplementaryData { get; set; } /// /// The method returns the change of status from the BSM. @@ -75,46 +75,47 @@ public class BSM : ITypeB /// The change of status. private static string GetChangeOfStatus(string bsm) { - int index = bsm.IndexOf('.'); + string changeOfStatus = bsm.Substring(0, 3); - if (index > -1) + if (changeOfStatus == Change) { - string header = bsm.Substring(0, index); - - if (header.Contains(Change)) - { - return Change; - } - else if (header.Contains(Delete)) - { - return Delete; - } - else - { - return Add; - } + return Change; + } + else if (changeOfStatus == Delete) + { + return Delete; + } + else + { + return Add; } - - return string.Empty; } /// public void Parse(string typeBString) { - ChangeOfStatus = GetChangeOfStatus(typeBString); + //Remove the end identifier because it's no longer needed. + //This needs to be removed before start else you end up with only END. + typeBString = typeBString.Replace($"{EndOfBSM}{Environment.NewLine}", string.Empty); + typeBString = typeBString.Replace(EndOfBSM, string.Empty); - //Remove the start & end identifiers because they're no longer needed. + //Remove the start identifier because it's no longer needed. + typeBString = typeBString.Replace($"{StartOfBSM}{Environment.NewLine}", string.Empty); typeBString = typeBString.Replace(StartOfBSM, string.Empty); - typeBString = typeBString.Replace(EndOfBSM, string.Empty); + + //Get the change of status and then remove it because its no longer needed + ChangeOfStatus = GetChangeOfStatus(typeBString); + typeBString = typeBString.Replace($"{ChangeOfStatus}{Environment.NewLine}", string.Empty); + typeBString = typeBString.Replace(ChangeOfStatus, string.Empty); int totalBytesProcessed = 0; do { - int startIndex = typeBString.IndexOf('.'); + int startIndex = typeBString.IndexOf('.', totalBytesProcessed); //Dot was not found so exit. - if (startIndex > -1) + if (startIndex == -1) { break; } diff --git a/JMayer.Example.WindowsService/BSM/BSMPDU.cs b/JMayer.Example.WindowsService/BSM/BSMPDU.cs index 6a80730..9fd1fcc 100644 --- a/JMayer.Example.WindowsService/BSM/BSMPDU.cs +++ b/JMayer.Example.WindowsService/BSM/BSMPDU.cs @@ -23,6 +23,28 @@ public override byte[] ToBytes() /// public override List Validate() { - return ValidateDataAnnotations(); + List validationResults = []; + + if (BSM.BaggageTagDetails != null) + { + Validator.TryValidateObject(BSM.BaggageTagDetails, new ValidationContext(BSM.BaggageTagDetails), validationResults, validateAllProperties: true); + } + + if (BSM.OutboundFlight != null) + { + Validator.TryValidateObject(BSM.OutboundFlight, new ValidationContext(BSM.OutboundFlight), validationResults, validateAllProperties: true); + } + + if (BSM.PassengerName != null) + { + Validator.TryValidateObject(BSM.PassengerName, new ValidationContext(BSM.PassengerName), validationResults, validateAllProperties: true); + } + + if (BSM.VersionSupplementaryData != null) + { + Validator.TryValidateObject(BSM.VersionSupplementaryData, new ValidationContext(BSM.VersionSupplementaryData), validationResults, validateAllProperties: true); + } + + return validationResults; } } diff --git a/JMayer.Example.WindowsService/BSM/BSMParser.cs b/JMayer.Example.WindowsService/BSM/BSMParser.cs index 7490b5f..a34597c 100644 --- a/JMayer.Example.WindowsService/BSM/BSMParser.cs +++ b/JMayer.Example.WindowsService/BSM/BSMParser.cs @@ -28,12 +28,25 @@ protected override PDUParserResult SubClassParse(byte[] bytes) int endIndex = bytesAsString.IndexOf(BSM.EndOfBSM, startIndex); - //End was not found or start is actual end, exit. + //End was not found or start is actually the end, exit. if (endIndex == -1 || startIndex == endIndex) { break; } + //Add the length of the end of BSM so the end is included. + endIndex += BSM.EndOfBSM.Length; + + //Ensures the new line is included, if it exists. + if (endIndex < bytesAsString.Length && bytesAsString[endIndex] == '\n') + { + endIndex++; + } + else if (endIndex + 1 < bytesAsString.Length && bytesAsString[endIndex] == '\r' && bytesAsString[endIndex + 1] == '\n') + { + endIndex += 2; + } + string bsmString = bytesAsString.Substring(startIndex, endIndex - startIndex); BSMPDU pdu = new(); diff --git a/JMayer.Example.WindowsService/BSM/BaggageTagDetailEqualityComparer.cs b/JMayer.Example.WindowsService/BSM/BaggageTagDetailEqualityComparer.cs new file mode 100644 index 0000000..59e5741 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/BaggageTagDetailEqualityComparer.cs @@ -0,0 +1,47 @@ +using System.Diagnostics.CodeAnalysis; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages comparing two BaggageTagDetail objects. +/// +public class BaggageTagDetailEqualityComparer : IEqualityComparer +{ + /// + public bool Equals(BaggageTagDetails? x, BaggageTagDetails? y) + { + if (x == null && y == null) + { + return true; + } + else if (x != null && y != null) + { + if (x.Count != y.Count) + { + return false; + } + else + { + foreach (string tag in x.BaggageTagNumbers) + { + if (!y.BaggageTagNumbers.Contains(tag)) + { + return false; + } + } + + return true; + } + } + else + { + return false; + } + } + + /// + public int GetHashCode([DisallowNull] BaggageTagDetails obj) + { + throw new NotImplementedException(); + } +} diff --git a/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs b/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs index a21646b..ca8ab8a 100644 --- a/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs +++ b/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs @@ -18,7 +18,6 @@ public int Count /// /// The property gets a list of baggage tag numbers. /// - /// [Length(1, 999)] public List BaggageTagNumbers { get; init; } = []; diff --git a/JMayer.Example.WindowsService/BSM/DateTimeExtensions.cs b/JMayer.Example.WindowsService/BSM/DateTimeExtensions.cs new file mode 100644 index 0000000..60beb33 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/DateTimeExtensions.cs @@ -0,0 +1,15 @@ +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The statis class contains extension methods for the DateTime. +/// +public static class DateTimeExtensions +{ + /// + /// The method returns the date time as a string in the 01JAN format. + /// + /// The date time to be formatted. + /// A formatted string. + public static string ToDayMonthFormat(this DateTime dateTime) + => $"{dateTime.Day:00}{dateTime.ToString("MMMM").Substring(0, 3).ToUpper()}"; +} diff --git a/JMayer.Example.WindowsService/BSM/OutboundFlight.cs b/JMayer.Example.WindowsService/BSM/OutboundFlight.cs index e78d879..c7dda73 100644 --- a/JMayer.Example.WindowsService/BSM/OutboundFlight.cs +++ b/JMayer.Example.WindowsService/BSM/OutboundFlight.cs @@ -12,19 +12,19 @@ public class OutboundFlight : ITypeB /// [Required] [RegularExpression("^[A-Z0-9]{2}$", ErrorMessage = "The airline must be 2 alphanumeric characters.")] - public string Airline { get; private set; } = string.Empty; + public string Airline { get; set; } = string.Empty; /// /// The property gets the class of travel for passenger for this flight. /// [RegularExpression("^([A-Z]{1})?$", ErrorMessage = "The class of travel must be 1 capital letter or empty.")] - public string ClassOfTravel { get; private set; } = string.Empty; + public string ClassOfTravel { get; set; } = string.Empty; /// /// The property gets the destination for this flight. /// [RegularExpression("^([A-Z]{3})?$", ErrorMessage = "The destination must be 3 letters or empty.")] - public string Destination { get; private set; } = string.Empty; + public string Destination { get; set; } = string.Empty; /// /// The constant for the .F element. @@ -35,18 +35,18 @@ public class OutboundFlight : ITypeB /// The property gets the date this flight flies. /// /// - /// This will be formatted like JAN01. + /// This will be formatted like 01JAN. /// [Required] - [RegularExpression("^(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)[0-9]{2}$", ErrorMessage = "The flight date must be the first three letters of the month, capitalized, followed by the day of month, 2 digits.")] - public string FlightDate { get; private set; } = string.Empty; + [RegularExpression("^[0-9]{2}(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)$", ErrorMessage = "The flight date must be the day of the month (2 digits) followed by the first three letters of the month (capitalized).")] + public string FlightDate { get; set; } = string.Empty; /// /// The property gets the identifier for the flight. /// [Required] [RegularExpression("^([0-9]{4})|([0-9]{4}[A-Z]{1})$", ErrorMessage = "The flight number must be 4 digits and optionally a capital letter.")] - public string FlightNumber { get; private set; } = string.Empty; + public string FlightNumber { get; set; } = string.Empty; /// public void Parse(string typeBString) diff --git a/JMayer.Example.WindowsService/BSM/OutboundFlightEqualityComparer.cs b/JMayer.Example.WindowsService/BSM/OutboundFlightEqualityComparer.cs new file mode 100644 index 0000000..0715ea0 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/OutboundFlightEqualityComparer.cs @@ -0,0 +1,36 @@ +using System.Diagnostics.CodeAnalysis; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages comparing two OutboundFlight objects. +/// +public class OutboundFlightEqualityComparer : IEqualityComparer +{ + /// + public bool Equals(OutboundFlight? x, OutboundFlight? y) + { + if (x == null && y == null) + { + return true; + } + else if (x != null && y != null) + { + return x.Airline == y.Airline + && x.ClassOfTravel == y.ClassOfTravel + && x.Destination == y.Destination + && x.FlightDate == y.FlightDate + && x.FlightNumber == y.FlightNumber; + } + else + { + return false; + } + } + + /// + public int GetHashCode([DisallowNull] OutboundFlight obj) + { + throw new NotImplementedException(); + } +} diff --git a/JMayer.Example.WindowsService/BSM/PassengerNameEqualityComparer.cs b/JMayer.Example.WindowsService/BSM/PassengerNameEqualityComparer.cs new file mode 100644 index 0000000..23a59db --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/PassengerNameEqualityComparer.cs @@ -0,0 +1,47 @@ +using System.Diagnostics.CodeAnalysis; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages comparing two PassengerName objects. +/// +public class PassengerNameEqualityComparer : IEqualityComparer +{ + /// + public bool Equals(PassengerName? x, PassengerName? y) + { + if (x == null && y == null) + { + return true; + } + else if (x != null && y != null) + { + if (x.SurName != y.SurName || x.GivenNames.Count != y.GivenNames.Count) + { + return false; + } + else + { + foreach (string givenName in x.GivenNames) + { + if (!y.GivenNames.Contains(givenName)) + { + return false; + } + } + + return true; + } + } + else + { + return false; + } + } + + /// + public int GetHashCode([DisallowNull] PassengerName obj) + { + throw new NotImplementedException(); + } +} diff --git a/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs b/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs index c761d6d..62605a0 100644 --- a/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs +++ b/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs @@ -12,7 +12,7 @@ public class VersionSupplementaryData : ITypeB /// [Required] [RegularExpression("^([A-Z]{3})?$", ErrorMessage = "The airport code must be 3 letters or empty.")] - public string AirportCode { get; private set; } = string.Empty; + public string AirportCode { get; set; } = string.Empty; /// /// The property gets the baggage source indicator (local, transfer, remote or terminating). @@ -20,13 +20,13 @@ public class VersionSupplementaryData : ITypeB /// [Required] [RegularExpression("^(L|R|X|T)$", ErrorMessage = "The baggage source indicator must be L, R, X or T.")] - public string BaggageSourceIndicator { get; private set; } = string.Empty; + public string BaggageSourceIndicator { get; set; } = string.Empty; /// /// The property gets the version number for the data dictionary. /// [Required] - public int DataDictionaryVersionNumber { get; private set; } + public int DataDictionaryVersionNumber { get; set; } /// /// The constant for the .V element. diff --git a/JMayer.Example.WindowsService/BSM/VersionSupplementaryDataEqualityComparer.cs b/JMayer.Example.WindowsService/BSM/VersionSupplementaryDataEqualityComparer.cs new file mode 100644 index 0000000..0f6c3d5 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/VersionSupplementaryDataEqualityComparer.cs @@ -0,0 +1,34 @@ +using System.Diagnostics.CodeAnalysis; + +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages comparing two VersionSupplementaryData objects. +/// +public class VersionSupplementaryDataEqualityComparer : IEqualityComparer +{ + /// + public bool Equals(VersionSupplementaryData? x, VersionSupplementaryData? y) + { + if (x == null && y == null) + { + return true; + } + else if (x != null && y != null) + { + return x.AirportCode == y.AirportCode + && x.BaggageSourceIndicator == y.BaggageSourceIndicator + && x.DataDictionaryVersionNumber == y.DataDictionaryVersionNumber; + } + else + { + return false; + } + } + + /// + public int GetHashCode([DisallowNull] VersionSupplementaryData obj) + { + return obj.GetHashCode(); + } +} diff --git a/TestProject/Test/BSMParserUnitTest.cs b/TestProject/Test/BSMParserUnitTest.cs new file mode 100644 index 0000000..ba60b03 --- /dev/null +++ b/TestProject/Test/BSMParserUnitTest.cs @@ -0,0 +1,594 @@ +using JMayer.Example.WindowsService.BSM; +using JMayer.Net.ProtocolDataUnit; +using System.Text; + +namespace TestProject.Test; + +/// +/// The class manages testing the BSM parser. +/// +public class BSMParserUnitTest +{ + /// + /// The constant for the .F airline. + /// + private const string DotFAirline = "AA"; + + /// + /// The constant for the .F class of travel. + /// + private const string DotFClassOfTravel = "A"; + + /// + /// The constant for the .F destination. + /// + private const string DotFDestination = "MSY"; + + /// + /// The constant for the .F flight number. + /// + private const string DotFFlightNumber = "1234"; + + /// + /// The constant for the .F invalid flight number. + /// + private const string DotFInvalidFlightNumber = "ABCD"; + + /// + /// The constant for the .N tag number. + /// + private const string DotNTagNumber = "0001123456"; + + /// + /// The constant for the .P given name. + /// + private const string DotPGivenName = "PASSENGER"; + + /// + /// The constant for the .P surname. + /// + private const string DotPSurName = "TEST"; + + /// + /// The constant for the .V airport code. + /// + private const string DotVAirportCode = "MCO"; + + /// + /// The method verifies a BSM with all fields set can be parsed. + /// + [Fact] + public void VerifyAllFields() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.True(result.PDUs[0].IsValid, "The BSM is not valid."); + Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); + } + + /// + /// The method verifies a BSM with a bad .F airline will cause a validation issue. + /// + [Fact] + public void VerifyDotFAirlineValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline.ToLower(), + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(OutboundFlight.Airline), result.PDUs[0].ValidationResults.First().MemberNames); + } + + /// + /// The method verifies a BSM with a bad .F class of travel will cause a validation issue. + /// + [Fact] + public void VerifyDotFClassOfTravelValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel.ToLower(), + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(OutboundFlight.ClassOfTravel), result.PDUs[0].ValidationResults.First().MemberNames); + } + + /// + /// The method verifies a BSM with a bad .F destination will cause a validation issue. + /// + [Fact] + public void VerifyDotFDestinationValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination.ToLower(), + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(OutboundFlight.Destination), result.PDUs[0].ValidationResults.First().MemberNames); + } + + /// + /// The method verifies a BSM with a bad .F flight number will cause a validation issue. + /// + [Fact] + public void VerifyDotFFlightNumberValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFInvalidFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(OutboundFlight.FlightNumber), result.PDUs[0].ValidationResults.First().MemberNames); + } + + /// + /// The method verifies a BSM with a .F element with no class of travel can be parsed. + /// + [Fact] + public void VerifyDotFNoClassOfTravel() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.True(result.PDUs[0].IsValid, "The BSM is not valid."); + Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); + } + + /// + /// The method verifies a BSM with a .F element with no destination can be parsed; class of travel + /// will also not be set. + /// + [Fact] + public void VerifyDotFNoDestination() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.True(result.PDUs[0].IsValid, "The BSM is not valid."); + Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); + } + + /// + /// The method verifies a BSM with a .N element with multiple tags can be parsed. + /// + [Fact] + public void VerifyDotNMultipleTags() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber, "0001123457", "0001123458", "0001123459", "0001123460"], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.True(result.PDUs[0].IsValid, "The BSM is not valid."); + Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); + } + + /// + /// The method verifies a BSM with a .P element with multiple given names can be parsed. + /// + [Fact] + public void VerifyDotPMultipleGivenNames() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = ["Given Name 1", "Given Name 2", "Given Name 3"], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.True(result.PDUs[0].IsValid, "The BSM is not valid."); + Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); + } + + /// + /// The method verifies a BSM with a .P element with no given names can be parsed. + /// + [Fact] + public void VerifyDotPNoGivenNames() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.True(result.PDUs[0].IsValid, "The BSM is not valid."); + Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); + } + + /// + /// The method verfies multiple BSMs can be parsed. + /// + [Fact] + public void VerifyMultipleBSMs() + { + BSM bsm1 = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [$"{DotPGivenName}1"], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSM bsm2 = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = ["0001123457"], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [$"{DotPGivenName}2"], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = 1, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm1.ToTypeB() + bsm2.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Equal(2, result.PDUs.Count); + Assert.IsType(result.PDUs[0]); + Assert.IsType(result.PDUs[1]); + Assert.True(result.PDUs[0].IsValid, "BSM 1 is not valid."); + Assert.True(result.PDUs[1].IsValid, "BSM 2 is not valid."); + Assert.True(new BMSEqualityComparer().Equals(bsm1, ((BSMPDU)result.PDUs[0]).BSM), "BSM 1 does not equal the first BSM in the parser results."); + Assert.True(new BMSEqualityComparer().Equals(bsm2, ((BSMPDU)result.PDUs[1]).BSM), "BSM 2 does not equal the second BSM in the parser results."); + } +} diff --git a/TestProject/TestProject.csproj b/TestProject/TestProject.csproj new file mode 100644 index 0000000..53e930b --- /dev/null +++ b/TestProject/TestProject.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + + + + + + + + + + + From 3ae89f07bbd5cd72b3511c2de28495aafa453c69 Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Fri, 16 Aug 2024 15:48:43 -0400 Subject: [PATCH 03/11] Finished BSM Parser unit test; various fixes --- .../BSM/BaggageTagDetails.cs | 4 + .../BSM/OutboundFlight.cs | 6 +- .../BSM/PassengerName.cs | 4 +- .../BSM/VersionSupplementaryData.cs | 4 +- TestProject/Test/BSMParserUnitTest.cs | 364 +++++++++++++++++- 5 files changed, 361 insertions(+), 21 deletions(-) diff --git a/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs b/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs index ca8ab8a..52492dc 100644 --- a/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs +++ b/JMayer.Example.WindowsService/BSM/BaggageTagDetails.cs @@ -18,6 +18,10 @@ public int Count /// /// The property gets a list of baggage tag numbers. /// + /// + /// Index 0 must be the first tag and index N must be the last tag. Each tag + /// after the first must increment by 1. + /// [Length(1, 999)] public List BaggageTagNumbers { get; init; } = []; diff --git a/JMayer.Example.WindowsService/BSM/OutboundFlight.cs b/JMayer.Example.WindowsService/BSM/OutboundFlight.cs index c7dda73..ecab295 100644 --- a/JMayer.Example.WindowsService/BSM/OutboundFlight.cs +++ b/JMayer.Example.WindowsService/BSM/OutboundFlight.cs @@ -11,7 +11,7 @@ public class OutboundFlight : ITypeB /// The property gets the airline for the flight. /// [Required] - [RegularExpression("^[A-Z0-9]{2}$", ErrorMessage = "The airline must be 2 alphanumeric characters.")] + [RegularExpression("^[A-Z0-9]{2}$", ErrorMessage = "The airline must be 2 alphanumeric characters; the letters must be capital.")] public string Airline { get; set; } = string.Empty; /// @@ -23,7 +23,7 @@ public class OutboundFlight : ITypeB /// /// The property gets the destination for this flight. /// - [RegularExpression("^([A-Z]{3})?$", ErrorMessage = "The destination must be 3 letters or empty.")] + [RegularExpression("^([A-Z]{3})?$", ErrorMessage = "The destination must be 3 capital letters or empty.")] public string Destination { get; set; } = string.Empty; /// @@ -38,7 +38,7 @@ public class OutboundFlight : ITypeB /// This will be formatted like 01JAN. /// [Required] - [RegularExpression("^[0-9]{2}(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)$", ErrorMessage = "The flight date must be the day of the month (2 digits) followed by the first three letters of the month (capitalized).")] + [RegularExpression("^[0-9]{2}(JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)$", ErrorMessage = "The flight date must be the day of the month (2 digits) followed by the first three capital letters of the month.")] public string FlightDate { get; set; } = string.Empty; /// diff --git a/JMayer.Example.WindowsService/BSM/PassengerName.cs b/JMayer.Example.WindowsService/BSM/PassengerName.cs index dd27170..3fcf4ba 100644 --- a/JMayer.Example.WindowsService/BSM/PassengerName.cs +++ b/JMayer.Example.WindowsService/BSM/PassengerName.cs @@ -38,11 +38,11 @@ public void Parse(string typeBString) { //The number of given names can be infront of the surname as either a 1 or 2 digit number //so remove the number if it exists. - if (Regex.IsMatch(elements[0].AsSpan(0, 2), "^\\d$")) + if (elements[0].Length >= 2 && Regex.IsMatch(elements[0].AsSpan(0, 2), "^\\d$")) { SurName = elements[0].Substring(2); } - else if (Regex.IsMatch(elements[0].AsSpan(0, 1), "^\\d$")) + else if (elements[0].Length >= 1 && Regex.IsMatch(elements[0].AsSpan(0, 1), "^\\d$")) { SurName = elements[0].Substring(1); } diff --git a/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs b/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs index 62605a0..f1893b4 100644 --- a/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs +++ b/JMayer.Example.WindowsService/BSM/VersionSupplementaryData.cs @@ -11,13 +11,12 @@ public class VersionSupplementaryData : ITypeB /// The property gets the aiport who sent the BSM. /// [Required] - [RegularExpression("^([A-Z]{3})?$", ErrorMessage = "The airport code must be 3 letters or empty.")] + [RegularExpression("^[A-Z]{3}$", ErrorMessage = "The airport code must be 3 capital letters.")] public string AirportCode { get; set; } = string.Empty; /// /// The property gets the baggage source indicator (local, transfer, remote or terminating). /// - /// [Required] [RegularExpression("^(L|R|X|T)$", ErrorMessage = "The baggage source indicator must be L, R, X or T.")] public string BaggageSourceIndicator { get; set; } = string.Empty; @@ -26,6 +25,7 @@ public class VersionSupplementaryData : ITypeB /// The property gets the version number for the data dictionary. /// [Required] + [Range(1, 9)] public int DataDictionaryVersionNumber { get; set; } /// diff --git a/TestProject/Test/BSMParserUnitTest.cs b/TestProject/Test/BSMParserUnitTest.cs index ba60b03..d52f6a8 100644 --- a/TestProject/Test/BSMParserUnitTest.cs +++ b/TestProject/Test/BSMParserUnitTest.cs @@ -29,6 +29,11 @@ public class BSMParserUnitTest /// private const string DotFFlightNumber = "1234"; + /// + /// The constant for the .F invalid flight date. + /// + private const string DotFInvalidFlightDate = "JAN01"; + /// /// The constant for the .F invalid flight number. /// @@ -54,6 +59,21 @@ public class BSMParserUnitTest /// private const string DotVAirportCode = "MCO"; + /// + /// The constant for the .V data dictionary version number. + /// + private const int DotVDataDictionaryVersionNumber = 1; + + /// + /// The constant for the .V invalid baggage source indicator. + /// + private const string DotVInvalidBaggageSourceIndicator = "A"; + + /// + /// The constant for the .V invalid data dictionary version number. + /// + private const int DotVInvalidDataDictionaryVersionNumber = 0; + /// /// The method verifies a BSM with all fields set can be parsed. /// @@ -84,7 +104,7 @@ public void VerifyAllFields() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -130,7 +150,7 @@ public void VerifyDotFAirlineValidation() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -177,7 +197,7 @@ public void VerifyDotFClassOfTravelValidation() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -224,7 +244,7 @@ public void VerifyDotFDestinationValidation() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -241,6 +261,53 @@ public void VerifyDotFDestinationValidation() Assert.Contains(nameof(OutboundFlight.Destination), result.PDUs[0].ValidationResults.First().MemberNames); } + /// + /// The method verifies a BSM with a bad .F flight date will cause a validation issue. + /// + [Fact] + public void VerifyDotFFlightDateValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DotFInvalidFlightDate, + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(OutboundFlight.FlightDate), result.PDUs[0].ValidationResults.First().MemberNames); + } + /// /// The method verifies a BSM with a bad .F flight number will cause a validation issue. /// @@ -271,7 +338,7 @@ public void VerifyDotFFlightNumberValidation() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -317,7 +384,7 @@ public void VerifyDotFNoClassOfTravel() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -362,7 +429,7 @@ public void VerifyDotFNoDestination() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -408,7 +475,7 @@ public void VerifyDotNMultipleTags() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -424,6 +491,56 @@ public void VerifyDotNMultipleTags() Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); } + /// + /// The method verifies a BSM with a badly formatted .N will cause a validation issue. + /// + [Fact] + public void VerifyDotNTagValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + //Increasing Count from 001 to 0001 to force .N to have too many characters. That will create a range validation issue + //because the parser will fail to parse the .N element which leave the list empty. + bsmString = bsmString.Replace($"{DotNTagNumber}{bsm.BaggageTagDetails.Count:D3}", $"{DotNTagNumber}{bsm.BaggageTagDetails.Count:D4}"); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(BaggageTagDetails.BaggageTagNumbers), result.PDUs[0].ValidationResults.First().MemberNames); + } + /// /// The method verifies a BSM with a .P element with multiple given names can be parsed. /// @@ -454,7 +571,7 @@ public void VerifyDotPMultipleGivenNames() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -499,7 +616,7 @@ public void VerifyDotPNoGivenNames() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -515,6 +632,194 @@ public void VerifyDotPNoGivenNames() Assert.True(new BMSEqualityComparer().Equals(bsm, ((BSMPDU)result.PDUs[0]).BSM), "The BSM does not equal the BSM in the parser results."); } + /// + /// The method verifies a BSM with a bad .P surname will cause a validation issue. + /// + [Fact] + public void VerifyDotPSurNameValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = string.Empty, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(PassengerName.SurName), result.PDUs[0].ValidationResults.First().MemberNames); + } + + /// + /// The method verifies a BSM with a bad .V airport code will cause a validation issue. + /// + [Fact] + public void VerifyDotVAirportCodeValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode.ToLower(), + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(VersionSupplementaryData.AirportCode), result.PDUs[0].ValidationResults.First().MemberNames); + } + + /// + /// The method verifies a BSM with a bad .V baggage source indicator will cause a validation issue. + /// + [Fact] + public void VerifyDotVBaggageSourceIndicatorValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = DotVInvalidBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(VersionSupplementaryData.BaggageSourceIndicator), result.PDUs[0].ValidationResults.First().MemberNames); + } + + /// + /// The method verifies a BSM with a bad .V data dictionary version number will cause a validation issue. + /// + [Fact] + public void VerifyDotVDataDictionaryVersionNumberValidation() + { + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [DotNTagNumber], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [DotPGivenName], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVInvalidDataDictionaryVersionNumber, + }, + }; + + BSMParser parser = new(); + string bsmString = bsm.ToTypeB(); + byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); + + PDUParserResult result = parser.Parse(bsmBytes); + + Assert.Single(result.PDUs); + Assert.IsType(result.PDUs[0]); + Assert.False(result.PDUs[0].IsValid, "The BSM is valid. It's expected to be invalid."); + Assert.Single(result.PDUs[0].ValidationResults); + Assert.Contains(nameof(VersionSupplementaryData.DataDictionaryVersionNumber), result.PDUs[0].ValidationResults.First().MemberNames); + } + /// /// The method verfies multiple BSMs can be parsed. /// @@ -545,7 +850,7 @@ public void VerifyMultipleBSMs() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; @@ -573,22 +878,53 @@ public void VerifyMultipleBSMs() { AirportCode = DotVAirportCode, BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, - DataDictionaryVersionNumber = 1, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, + }, + }; + + BSM bsm3 = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = ["0001123458"], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = DotFAirline, + ClassOfTravel = DotFClassOfTravel, + Destination = DotFDestination, + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = DotFFlightNumber, + }, + PassengerName = new() + { + GivenNames = [$"{DotPGivenName}2"], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, }, }; BSMParser parser = new(); - string bsmString = bsm1.ToTypeB() + bsm2.ToTypeB(); + string bsmString = bsm1.ToTypeB() + bsm2.ToTypeB() + bsm3.ToTypeB(); byte[] bsmBytes = Encoding.ASCII.GetBytes(bsmString); PDUParserResult result = parser.Parse(bsmBytes); - Assert.Equal(2, result.PDUs.Count); + Assert.Equal(3, result.PDUs.Count); Assert.IsType(result.PDUs[0]); Assert.IsType(result.PDUs[1]); + Assert.IsType(result.PDUs[2]); Assert.True(result.PDUs[0].IsValid, "BSM 1 is not valid."); Assert.True(result.PDUs[1].IsValid, "BSM 2 is not valid."); + Assert.True(result.PDUs[2].IsValid, "BSM 3 is not valid."); Assert.True(new BMSEqualityComparer().Equals(bsm1, ((BSMPDU)result.PDUs[0]).BSM), "BSM 1 does not equal the first BSM in the parser results."); Assert.True(new BMSEqualityComparer().Equals(bsm2, ((BSMPDU)result.PDUs[1]).BSM), "BSM 2 does not equal the second BSM in the parser results."); + Assert.True(new BMSEqualityComparer().Equals(bsm3, ((BSMPDU)result.PDUs[2]).BSM), "BSM 3 does not equal the third BSM in the parser results."); } } From dfcea7ccc67770e897ce6075a3c40b503a74a58f Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Fri, 16 Aug 2024 17:28:01 -0400 Subject: [PATCH 04/11] Added Workers --- .../BSMClientWorker.cs | 93 +++++++++++++++ .../BSMServerConnectionWorker.cs | 110 ++++++++++++++++++ .../BSMServerOutputWorker.cs | 81 +++++++++++++ JMayer.Example.WindowsService/Program.cs | 14 ++- JMayer.Example.WindowsService/Worker.cs | 24 ---- 5 files changed, 297 insertions(+), 25 deletions(-) create mode 100644 JMayer.Example.WindowsService/BSMClientWorker.cs create mode 100644 JMayer.Example.WindowsService/BSMServerConnectionWorker.cs create mode 100644 JMayer.Example.WindowsService/BSMServerOutputWorker.cs delete mode 100644 JMayer.Example.WindowsService/Worker.cs diff --git a/JMayer.Example.WindowsService/BSMClientWorker.cs b/JMayer.Example.WindowsService/BSMClientWorker.cs new file mode 100644 index 0000000..f6fdc15 --- /dev/null +++ b/JMayer.Example.WindowsService/BSMClientWorker.cs @@ -0,0 +1,93 @@ +using JMayer.Example.WindowsService.BSM; +using JMayer.Net; +using JMayer.Net.ProtocolDataUnit; + +namespace JMayer.Example.WindowsService; + +/// +/// The class manages connecting to the server and processes BSMs received from the server. +/// +internal class BSMClientWorker : BackgroundService +{ + /// + /// Used to log activity for the service. + /// + private readonly ILogger _logger; + + /// + /// Used to manage TCP/IP communication. + /// + private readonly IClient _client; + + /// + /// The dependency injection constructor. + /// + /// Used to log activity for the service. + /// Used to manage TCP/IP communication. + public BSMClientWorker(ILogger logger, IClient client) + { + _logger = logger; + _client = client; + } + + /// + /// The method manages connecting to the server and processes BSMs received from the server. + /// + /// Used to cancel the task when the service stops. + /// A Task object for the async. + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (!_client.IsConnected) + { + try + { + await _client.ConnectAsync("127.0.0.1", 55555, stoppingToken); + _logger.LogInformation("The client connected to the BSM server."); + } + catch (Exception ex) + { + _logger.LogError(ex, "The client failed to connect to the BSM server."); + } + } + else + { + List pdus = await _client.ReceiveAndParseAsync(stoppingToken); + + foreach (BSMPDU pdu in pdus.Cast()) + { + if (pdu.IsValid) + { + _logger.LogInformation("The client received a valid BSM from the server. {BSM}", pdu.BSM.ToTypeB()); + } + else + { + _logger.LogWarning("The client received an invalid BSM from the server. {BSM}", pdu.BSM.ToTypeB()); + } + } + } + + await Task.Delay(1000); + } + } + + /// + /// + /// This is overriden so the client can disconnect from the server. + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + try + { + _client.Disconnect(); + _logger.LogInformation("The client disconnect from the server."); + } + catch (Exception ex) + { + _logger.LogError(ex, "The client failed to disconnect from the server; it may have already been disconnected by the server."); + } + + await base.StopAsync(cancellationToken); + } +} diff --git a/JMayer.Example.WindowsService/BSMServerConnectionWorker.cs b/JMayer.Example.WindowsService/BSMServerConnectionWorker.cs new file mode 100644 index 0000000..ebc62d7 --- /dev/null +++ b/JMayer.Example.WindowsService/BSMServerConnectionWorker.cs @@ -0,0 +1,110 @@ +using JMayer.Net; + +namespace JMayer.Example.WindowsService; + +/// +/// The class manages starting/stopping the server & receiving remote connections from the clients. +/// +/// +/// The server code is split between two workers because you want to accept client connections +/// as frequently as possible; on a real server, multiple client connections may be queued to be +/// accepted but if the worker accepts a connection, does other stuff and then sleeps for a second +/// or more, the queued clients are missing BSMs. +/// +internal class BSMServerConnectionWorker : BackgroundService +{ + /// + /// Used to log activity for the service. + /// + private readonly ILogger _logger; + + /// + /// Used to manage TCP/IP communication. + /// + private readonly IServer _server; + + /// + /// The port to monitor. + /// + public const int Port = 55555; + + /// + /// The dependency injection constructor. + /// + /// Used to log activity for the service. + /// Used to manage TCP/IP communication. + public BSMServerConnectionWorker(ILogger logger, IServer server) + { + _logger = logger; + _server = server; + } + + /// + /// The method handles starting the server & receiving remote connections from the clients. + /// + /// Used to cancel the task when the service stops. + /// A Task object for the async. + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _server.ConnectionStaleMode = ConnectionStaleMode.LastSent; + _server.ConnectionTimeout = 20; + + while (!stoppingToken.IsCancellationRequested) + { + //Start the server if not ready. + if (!_server.IsReady) + { + try + { + _server.Start(Port); + _logger.LogInformation("The BSM server is listen on {Port} port.", Port); + } + catch (Exception ex) + { + _logger.LogError(ex, "The BSM server failed to listen to the {Port} port", Port); + } + } + //Accept the client connections. + else + { + try + { + Guid id = await _server.AcceptIncomingConnectionAsync(stoppingToken); + + if (id != Guid.Empty) + { + _logger.LogInformation("The BSM server accepted a remote client connection."); + } + } + catch (Exception ex) + { + _logger.LogError(ex, "The BSM server failed to accept a remote client connection."); + } + } + + await Task.Delay(10, stoppingToken); + } + } + + /// + /// + /// This is overriden so the server can stop listening on the port. + /// + public override async Task StopAsync(CancellationToken cancellationToken) + { + if (_server.IsReady) + { + try + { + _server.Stop(); + _logger.LogInformation("The BSM server has stopped."); + } + catch (Exception ex) + { + _logger.LogError(ex, "The BSM server failed to stop; the service will still stop."); + } + } + + await base.StopAsync(cancellationToken); + } +} diff --git a/JMayer.Example.WindowsService/BSMServerOutputWorker.cs b/JMayer.Example.WindowsService/BSMServerOutputWorker.cs new file mode 100644 index 0000000..34443e0 --- /dev/null +++ b/JMayer.Example.WindowsService/BSMServerOutputWorker.cs @@ -0,0 +1,81 @@ +using JMayer.Example.WindowsService.BSM; +using JMayer.Net; + +namespace JMayer.Example.WindowsService; + +/// +/// The class manages stale connections and sending the BSMs to the clients. +/// +internal class BSMServerOutputWorker : BackgroundService +{ + /// + /// Used to log activity for the service. + /// + private readonly ILogger _logger; + + /// + /// Used to manage TCP/IP communication. + /// + private readonly IServer _server; + + /// + /// The dependency injection constructor. + /// + /// Used to log activity for the service. + /// Used to manage TCP/IP communication. + public BSMServerOutputWorker(ILogger logger, IServer server) + { + _logger = logger; + _server = server; + } + + /// + /// The class manages client connections and sending BSMs to the clients. + /// + /// Used to cancel the task when the service stops. + /// A Task object for the async. + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + while (!stoppingToken.IsCancellationRequested) + { + if (_server.IsReady) + { + //Manage stale remote clients. + List connectionIds = _server.GetStaleRemoteConnections(); + + if (connectionIds.Count > 0) + { + _logger.LogInformation("The BSM server detected stale remote clients; will attempt to disconnect."); + + foreach (Guid connectionId in connectionIds) + { + try + { + _server.Disconnect(connectionId); + } + catch { } + } + } + + //Generate a BSM & sends it to the remote clients. + try + { + BSM.BSM bsm = new(); //Need to generate; should make a class to manage the generation. + BSMPDU pdu = new() + { + BSM = bsm, + }; + + await _server.SendToAllAsync(pdu, stoppingToken); + _logger.LogInformation("The BSM server sent a BSM to the remote clients. {BSM}", pdu.BSM.ToTypeB()); + } + catch (Exception ex) + { + _logger.LogError(ex, "The BSM server failed to send the BSM to the remote clients."); + } + } + + await Task.Delay(10_000, stoppingToken); + } + } +} diff --git a/JMayer.Example.WindowsService/Program.cs b/JMayer.Example.WindowsService/Program.cs index a2c8451..e1b6b2d 100644 --- a/JMayer.Example.WindowsService/Program.cs +++ b/JMayer.Example.WindowsService/Program.cs @@ -1,7 +1,19 @@ using JMayer.Example.WindowsService; +using JMayer.Example.WindowsService.BSM; +using JMayer.Net.TcpIp; var builder = Host.CreateApplicationBuilder(args); -builder.Services.AddHostedService(); +builder.Services.AddWindowsService(options => +{ + options.ServiceName = "Example BSM Service"; +}); + +builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); +builder.Services.AddHostedService(); + var host = builder.Build(); host.Run(); diff --git a/JMayer.Example.WindowsService/Worker.cs b/JMayer.Example.WindowsService/Worker.cs deleted file mode 100644 index b15d9f2..0000000 --- a/JMayer.Example.WindowsService/Worker.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace JMayer.Example.WindowsService -{ - public class Worker : BackgroundService - { - private readonly ILogger _logger; - - public Worker(ILogger logger) - { - _logger = logger; - } - - protected override async Task ExecuteAsync(CancellationToken stoppingToken) - { - while (!stoppingToken.IsCancellationRequested) - { - if (_logger.IsEnabled(LogLevel.Information)) - { - _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now); - } - await Task.Delay(1000, stoppingToken); - } - } - } -} From 1620e31bd87e3d4316a334c8a493abcc157d0d78 Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Sat, 17 Aug 2024 10:59:41 -0400 Subject: [PATCH 05/11] Added IATA Generator & Unit Test --- .../BSM/IATAGenerator.cs | 53 ++++++++++++++ TestProject/Test/IATAGeneratorUnitTest.cs | 69 +++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 JMayer.Example.WindowsService/BSM/IATAGenerator.cs create mode 100644 TestProject/Test/IATAGeneratorUnitTest.cs diff --git a/JMayer.Example.WindowsService/BSM/IATAGenerator.cs b/JMayer.Example.WindowsService/BSM/IATAGenerator.cs new file mode 100644 index 0000000..42800ce --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/IATAGenerator.cs @@ -0,0 +1,53 @@ +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages generating an IATA (10 digit tag number). +/// +public class IATAGenerator +{ + /// + /// Keeps track of the sequence number to be used. + /// + private int _sequenceNumber = MinSequenceNumber; + + /// + /// The property gets/sets the alphanumeric airline code. + /// + /// + /// Used by the BSM generator. + /// + public string AirlineAlphaNumericCode { get; init; } = string.Empty; + + /// + /// The property gets/sets the numeric airline code used by the IATA. + /// + public string AirlineNumericCode { get; init; } = string.Empty; + + /// + /// The constant for the maximum sequence number. + /// + public const int MaxSequenceNumber = 999999; + + /// + /// The constant for the minimum sequence number. + /// + public const int MinSequenceNumber = 1; + + /// + /// The method returns the next IATA. + /// + /// The IATA. + public string Generate() + { + string iata = $"0{AirlineNumericCode}{_sequenceNumber.ToString().PadLeft(6, '0')}"; + + _sequenceNumber++; + + if (_sequenceNumber > MaxSequenceNumber) + { + _sequenceNumber = MinSequenceNumber; + } + + return iata; + } +} diff --git a/TestProject/Test/IATAGeneratorUnitTest.cs b/TestProject/Test/IATAGeneratorUnitTest.cs new file mode 100644 index 0000000..6618b8c --- /dev/null +++ b/TestProject/Test/IATAGeneratorUnitTest.cs @@ -0,0 +1,69 @@ +using JMayer.Example.WindowsService.BSM; + +namespace TestProject.Test; + +/// +/// The class manages testing the IATA generator. +/// +public class IATAGeneratorUnitTest +{ + /// + /// The constant for the airline numeric code. + /// + private const string AirlineNumericCode = "001"; + + /// + /// The method verifies an IATA will be generated. + /// + [Fact] + public void VerifyGeneration() + { + IATAGenerator iataGenerator = new() + { + AirlineNumericCode = AirlineNumericCode, + }; + string iata = iataGenerator.Generate(); + + Assert.Equal($"0{iataGenerator.AirlineNumericCode}{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", iata); + } + + /// + /// The method verifies if 3 IATAs are generated, each is incremented by 1. + /// + [Fact] + public void VerifyIncrementByOne() + { + IATAGenerator iataGenerator = new() + { + AirlineNumericCode = AirlineNumericCode, + }; + + string iata1 = iataGenerator.Generate(); + string iata2 = iataGenerator.Generate(); + string iata3 = iataGenerator.Generate(); + + Assert.Equal($"0{iataGenerator.AirlineNumericCode}{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", iata1); + Assert.Equal($"0{iataGenerator.AirlineNumericCode}{(IATAGenerator.MinSequenceNumber + 1).ToString().PadLeft(6, '0')}", iata2); + Assert.Equal($"0{iataGenerator.AirlineNumericCode}{(IATAGenerator.MinSequenceNumber + 2).ToString().PadLeft(6, '0')}", iata3); + } + + /// + /// The method verifies the generator will rollover after 999999 IATAs are generated. + /// + [Fact] + public void VerifyRollover() + { + string iata = string.Empty; + IATAGenerator iataGenerator = new() + { + AirlineNumericCode = AirlineNumericCode, + }; + + for (int index = 0; index <= IATAGenerator.MaxSequenceNumber; index++) + { + iata = iataGenerator.Generate(); + } + + Assert.Equal($"0{iataGenerator.AirlineNumericCode}{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", iata); + } +} From d63d4664f705780aed3f5fd7cb66f869d43dc66c Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:33:05 -0400 Subject: [PATCH 06/11] Added BSM Generator & Unit Test --- .../BSM/BSMGenerator.cs | 230 ++++++++++++++++++ .../BSM/IATAGenerator.cs | 4 + TestProject/Test/BSMGeneratorUnitTest.cs | 225 +++++++++++++++++ 3 files changed, 459 insertions(+) create mode 100644 JMayer.Example.WindowsService/BSM/BSMGenerator.cs create mode 100644 TestProject/Test/BSMGeneratorUnitTest.cs diff --git a/JMayer.Example.WindowsService/BSM/BSMGenerator.cs b/JMayer.Example.WindowsService/BSM/BSMGenerator.cs new file mode 100644 index 0000000..7b1f0a2 --- /dev/null +++ b/JMayer.Example.WindowsService/BSM/BSMGenerator.cs @@ -0,0 +1,230 @@ +namespace JMayer.Example.WindowsService.BSM; + +/// +/// The class manages generating a BSM. +/// +public class BSMGenerator +{ + /// + /// The class of travels. + /// + private readonly string[] _classOfTravels = + [ + "A", + "B", + "C", + "D", + "E", + "F", + "G", + "H", + "I", + "J", + "K", + "L", + "M", + "N", + "O", + "P", + "Q", + "R", + "S", + "T", + "U", + "V", + "W", + "X", + "Y", + "Z" + ]; + + /// + /// The destinations. + /// + private readonly string[] _destinations = + [ + "CAE", + "BNA", + "LBB", + "MSY", + "OAK", + "PIE", + "RSW", + "VPS", + ]; + + /// + /// The destination being used. + /// + private int _destinationIndex = 0; + + /// + /// The flight number for the BSM. + /// + private int _flightNumber = MinFlightNumber; + + /// + /// Used to generate IATAs. + /// + private readonly IATAGenerator[] _iataGenerators = + [ + new IATAGenerator() { AirlineAlphaNumericCode = "AA", AirlineNumericCode = "001", }, + new IATAGenerator() { AirlineAlphaNumericCode = "DL", AirlineNumericCode = "006", }, + new IATAGenerator() { AirlineAlphaNumericCode = "UA", AirlineNumericCode = "016", }, + new IATAGenerator() { AirlineAlphaNumericCode = "WN", AirlineNumericCode = "526", }, + ]; + + /// + /// The IATA generator being used. + /// + private int _iataGeneratorIndex = 0; + + /// + /// The passenger count. + /// + private int _passengerCount = MinPassengerCount; + + /// + /// The constant for the .P given name. + /// + private const string DotPGivenName = "PASSENGER"; + + /// + /// The constant for the .P surname. + /// + private const string DotPSurName = "TEST"; + + /// + /// The constant for the .V airport code. + /// + private const string DotVAirportCode = "MCO"; + + /// + /// The constant for the .V data dictionary version number. + /// + private const int DotVDataDictionaryVersionNumber = 1; + + /// + /// The constant for the maximum flight number. + /// + public const int MaxFlightNumber = 9999; + + /// + /// The constant for the minimum flight number. + /// + public const int MinFlightNumber = 1; + + /// + /// The constant for the maximum number of passengers. + /// + public const int MaxPassengerCount = 100; + + /// + /// The constant for the minimum number of passengers. + /// + public const int MinPassengerCount = 1; + + /// + /// The default constructor. + /// + public BSMGenerator() + { + SetRandomDestination(); + } + + /// + /// The method returns the next BSM. + /// + /// The BSM. + public BSM Generate() + { + IATAGenerator generator = _iataGenerators[_iataGeneratorIndex]; + + BSM bsm = new() + { + BaggageTagDetails = new() + { + BaggageTagNumbers = [generator.Generate()], + }, + ChangeOfStatus = BSM.Add, + OutboundFlight = new() + { + Airline = generator.AirlineAlphaNumericCode, + ClassOfTravel = _classOfTravels[new Random(DateTime.Now.Second).Next(0, _classOfTravels.Length - 1)], + Destination = _destinations[_destinationIndex], + FlightDate = DateTime.Today.ToDayMonthFormat(), + FlightNumber = _flightNumber.ToString().PadLeft(4, '0'), + }, + PassengerName = new() + { + GivenNames = [$"{DotPGivenName}{_passengerCount}"], + SurName = DotPSurName, + }, + VersionSupplementaryData = new() + { + AirportCode = DotVAirportCode, + BaggageSourceIndicator = VersionSupplementaryData.LocalBaggageSourceIndicator, + DataDictionaryVersionNumber = DotVDataDictionaryVersionNumber, + }, + }; + + IncrementPassengerCount(); + + return bsm; + } + + /// + /// The method increments the flight number. + /// + private void IncrementFlightNumber() + { + _flightNumber++; + + if (_flightNumber > MaxFlightNumber) + { + _flightNumber = MinFlightNumber; + } + } + + /// + /// The method increments to the next IATA generator. + /// + private void IncrementIATAGenerator() + { + _iataGeneratorIndex++; + + if (_iataGeneratorIndex == _iataGenerators.Length) + { + _iataGeneratorIndex = 0; + } + } + + /// + /// The method increments the passenger count. + /// + /// + /// When the maximum number of passengers are reached, the flight number is incremented, + /// a new IATA generator is used and a new destination is randomly chosen. The idea is the + /// generator will generate X BSMs for a flight and then a new one is used. + /// + private void IncrementPassengerCount() + { + _passengerCount++; + + if (_passengerCount > MaxPassengerCount) + { + _passengerCount = MinPassengerCount; + IncrementFlightNumber(); + IncrementIATAGenerator(); + SetRandomDestination(); + } + } + + /// + /// The method sets a random destination. + /// + private void SetRandomDestination() + { + _destinationIndex = new Random(DateTime.Now.Second).Next(0, _destinations.Length - 1); + } +} diff --git a/JMayer.Example.WindowsService/BSM/IATAGenerator.cs b/JMayer.Example.WindowsService/BSM/IATAGenerator.cs index 42800ce..8f82873 100644 --- a/JMayer.Example.WindowsService/BSM/IATAGenerator.cs +++ b/JMayer.Example.WindowsService/BSM/IATAGenerator.cs @@ -37,6 +37,10 @@ public class IATAGenerator /// The method returns the next IATA. /// /// The IATA. + /// + /// The IATA format is the first digit is 0 or 2-9, the next 3 digits are the airline numeric code + /// and the last 6 digits is a sequence number from 1-999999. + /// public string Generate() { string iata = $"0{AirlineNumericCode}{_sequenceNumber.ToString().PadLeft(6, '0')}"; diff --git a/TestProject/Test/BSMGeneratorUnitTest.cs b/TestProject/Test/BSMGeneratorUnitTest.cs new file mode 100644 index 0000000..17461b5 --- /dev/null +++ b/TestProject/Test/BSMGeneratorUnitTest.cs @@ -0,0 +1,225 @@ +using JMayer.Example.WindowsService.BSM; + +namespace TestProject.Test; + +/// +/// The class manages testing the BSM generator. +/// +public class BSMGeneratorUnitTest +{ + /// + /// The constant for the .P given name. + /// + private const string DotPGivenName = "PASSENGER"; + + /// + /// The constant for the .P surname. + /// + private const string DotPSurName = "TEST"; + + /// + /// The constant for the .V airport code. + /// + private const string DotVAirportCode = "MCO"; + + /// + /// The constant for the .V data dictionary version number. + /// + private const int DotVDataDictionaryVersionNumber = 1; + + /// + /// The constant for the maximum number of airlines used by the BSM generator. + /// + private const int MaxAirline = 4; + + /// + /// The method verifies the generator will rollover to the first airline after the maximum number is generated. + /// + [Fact] + public void VerifyAirlineRollover() + { + BSM bsm = new(); + BSMGenerator bsmGenerator = new(); + int maxPassengers = BSMGenerator.MaxPassengerCount * MaxAirline; + + for (int index = 0; index <= maxPassengers; index++) + { + bsm = bsmGenerator.Generate(); + } + + //Verify the baggage tag details. + Assert.NotNull(bsm.BaggageTagDetails); + Assert.Single(bsm.BaggageTagDetails.BaggageTagNumbers); + Assert.Equal($"0001{(BSMGenerator.MaxPassengerCount + 1).ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); + + //Verify the change of status. + Assert.Equal(BSM.Add, bsm.ChangeOfStatus); + + //Verify the outbound flight. + Assert.NotNull(bsm.OutboundFlight); + Assert.Equal("AA", bsm.OutboundFlight.Airline); + Assert.NotEmpty(bsm.OutboundFlight.ClassOfTravel); //Class of Travel is randomly set so just make sure its set to something. + Assert.NotEmpty(bsm.OutboundFlight.Destination); //Destination is randomly set so just make sure its set to something. + Assert.Equal(DateTime.Today.ToDayMonthFormat(), bsm.OutboundFlight.FlightDate); + Assert.Equal((MaxAirline + 1).ToString().PadLeft(4, '0'), bsm.OutboundFlight.FlightNumber); + + //Verify the passenger name. + Assert.NotNull(bsm.PassengerName); + Assert.Single(bsm.PassengerName.GivenNames); + Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + + //Verify the version supplementary data. + Assert.NotNull(bsm.VersionSupplementaryData); + Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); + Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + } + + /// + /// The method verifies the generator will rollover the flight number after the maximum number is generated. + /// + [Fact] + public void VerifyFlightNumberRollover() + { + BSM bsm = new(); + BSMGenerator bsmGenerator = new(); + int maxPassengers = BSMGenerator.MaxPassengerCount * BSMGenerator.MaxFlightNumber; + + for (int index = 0; index <= maxPassengers; index++) + { + bsm = bsmGenerator.Generate(); + } + + //Verify the baggage tag details. + Assert.NotNull(bsm.BaggageTagDetails); + Assert.Single(bsm.BaggageTagDetails.BaggageTagNumbers); + Assert.NotEmpty(bsm.BaggageTagDetails.BaggageTagNumbers[0]); //999,900 passengers are generated and I don't think there's an easy to know what the IATA number will be so just make sure its set. + + //Verify the change of status. + Assert.Equal(BSM.Add, bsm.ChangeOfStatus); + + //Verify the outbound flight. + Assert.NotNull(bsm.OutboundFlight); + Assert.NotEmpty(bsm.OutboundFlight.Airline); //999,900 passengers are generated and I don't think there's an easy to know what the airline will be so just make sure its set. + Assert.NotEmpty(bsm.OutboundFlight.ClassOfTravel); //Class of Travel is randomly set so just make sure its set to something. + Assert.NotEmpty(bsm.OutboundFlight.Destination); //Destination is randomly set so just make sure its set to something. + Assert.Equal(DateTime.Today.ToDayMonthFormat(), bsm.OutboundFlight.FlightDate); + Assert.Equal(BSMGenerator.MinFlightNumber.ToString().PadLeft(4, '0'), bsm.OutboundFlight.FlightNumber); + + //Verify the passenger name. + Assert.NotNull(bsm.PassengerName); + Assert.Single(bsm.PassengerName.GivenNames); + Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + + //Verify the version supplementary data. + Assert.NotNull(bsm.VersionSupplementaryData); + Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); + Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + } + + /// + /// The class verifies a BSM is generated. + /// + [Fact] + public void VerifyGeneration() + { + BSMGenerator bsmGenerator = new(); + BSM bsm = bsmGenerator.Generate(); + + //Verify the baggage tag details. + Assert.NotNull(bsm.BaggageTagDetails); + Assert.Single(bsm.BaggageTagDetails.BaggageTagNumbers); + Assert.Equal($"0001{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); + + //Verify the change of status. + Assert.Equal(BSM.Add, bsm.ChangeOfStatus); + + //Verify the outbound flight. + Assert.NotNull(bsm.OutboundFlight); + Assert.Equal("AA", bsm.OutboundFlight.Airline); + Assert.NotEmpty(bsm.OutboundFlight.ClassOfTravel); //Class of Travel is randomly set so just make sure its set to something. + Assert.NotEmpty(bsm.OutboundFlight.Destination); //Destination is randomly set so just make sure its set to something. + Assert.Equal(DateTime.Today.ToDayMonthFormat(), bsm.OutboundFlight.FlightDate); + Assert.Equal(BSMGenerator.MinFlightNumber.ToString().PadLeft(4, '0'), bsm.OutboundFlight.FlightNumber); + + //Verify the passenger name. + Assert.NotNull(bsm.PassengerName); + Assert.Single(bsm.PassengerName.GivenNames); + Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + + //Verify the version supplementary data. + Assert.NotNull(bsm.VersionSupplementaryData); + Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); + Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + } + + /// + /// The method verifies the passenger increments by one on each generation. + /// + [Fact] + public void VerifyPassengerIncrementsByOne() + { + BSMGenerator bsmGenerator = new(); + BSM bsm1 = bsmGenerator.Generate(); + BSM bsm2 = bsmGenerator.Generate(); + BSM bsm3 = bsmGenerator.Generate(); + + Assert.NotNull(bsm1.PassengerName); + Assert.Equal($"{DotPGivenName}1", bsm1.PassengerName.GivenNames[0]); + + Assert.NotNull(bsm2.PassengerName); + Assert.Equal($"{DotPGivenName}2", bsm2.PassengerName.GivenNames[0]); + + Assert.NotNull(bsm3.PassengerName); + Assert.Equal($"{DotPGivenName}3", bsm3.PassengerName.GivenNames[0]); + } + + /// + /// The method verifies the generator will rollover the passengers after the maximum number is generated. It also verifies a new airline and flight number are generated. + /// + [Fact] + public void VerifyPassengerRollover() + { + BSM bsm = new(); + BSMGenerator bsmGenerator = new(); + + for (int index = 0; index <= BSMGenerator.MaxPassengerCount; index++) + { + bsm = bsmGenerator.Generate(); + } + + //Verify the baggage tag details. + Assert.NotNull(bsm.BaggageTagDetails); + Assert.Single(bsm.BaggageTagDetails.BaggageTagNumbers); + //The sequence used by the BSM generator is 001, 006, 016, 526 so on the first passenger rollover, 006 will be used next. + Assert.Equal($"0006{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); + + //Verify the change of status. + Assert.Equal(BSM.Add, bsm.ChangeOfStatus); + + //Verify the outbound flight. + Assert.NotNull(bsm.OutboundFlight); + Assert.Equal("DL", bsm.OutboundFlight.Airline); //The sequence used by the BSM generator is AA, DL, UA, WN so on the first passenger rollover, DL will be used next. + Assert.NotEmpty(bsm.OutboundFlight.ClassOfTravel); //Class of Travel is randomly set so just make sure its set to something. + Assert.NotEmpty(bsm.OutboundFlight.Destination); //Destination is randomly set so just make sure its set to something. + Assert.Equal(DateTime.Today.ToDayMonthFormat(), bsm.OutboundFlight.FlightDate); + Assert.Equal((BSMGenerator.MinFlightNumber + 1).ToString().PadLeft(4, '0'), bsm.OutboundFlight.FlightNumber); + + //Verify the passenger name. + Assert.NotNull(bsm.PassengerName); + Assert.Single(bsm.PassengerName.GivenNames); + Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + + //Verify the version supplementary data. + Assert.NotNull(bsm.VersionSupplementaryData); + Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); + Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + } +} From c93763d547f6ca7c29576320bb97bca87affd5d1 Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:34:26 -0400 Subject: [PATCH 07/11] Worker uses BSM Generator; various fixes --- .../BSM/PassengerName.cs | 2 +- .../BSMServerConnectionWorker.cs | 3 -- .../BSMServerOutputWorker.cs | 47 ++++++++++--------- JMayer.Example.WindowsService/Program.cs | 6 ++- 4 files changed, 31 insertions(+), 27 deletions(-) diff --git a/JMayer.Example.WindowsService/BSM/PassengerName.cs b/JMayer.Example.WindowsService/BSM/PassengerName.cs index 3fcf4ba..7b51b5b 100644 --- a/JMayer.Example.WindowsService/BSM/PassengerName.cs +++ b/JMayer.Example.WindowsService/BSM/PassengerName.cs @@ -75,6 +75,6 @@ public string ToTypeB() givenNames += $"/{givenName}"; } - return $"{DotPElement}/{GivenNames.Count}{SurName}{givenNames}"; + return $"{DotPElement}/{GivenNames.Count}{SurName}{givenNames}{Environment.NewLine}"; } } diff --git a/JMayer.Example.WindowsService/BSMServerConnectionWorker.cs b/JMayer.Example.WindowsService/BSMServerConnectionWorker.cs index ebc62d7..92098ed 100644 --- a/JMayer.Example.WindowsService/BSMServerConnectionWorker.cs +++ b/JMayer.Example.WindowsService/BSMServerConnectionWorker.cs @@ -46,9 +46,6 @@ public BSMServerConnectionWorker(ILogger logger, ISer /// A Task object for the async. protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - _server.ConnectionStaleMode = ConnectionStaleMode.LastSent; - _server.ConnectionTimeout = 20; - while (!stoppingToken.IsCancellationRequested) { //Start the server if not ready. diff --git a/JMayer.Example.WindowsService/BSMServerOutputWorker.cs b/JMayer.Example.WindowsService/BSMServerOutputWorker.cs index 34443e0..5afc531 100644 --- a/JMayer.Example.WindowsService/BSMServerOutputWorker.cs +++ b/JMayer.Example.WindowsService/BSMServerOutputWorker.cs @@ -8,6 +8,11 @@ namespace JMayer.Example.WindowsService; /// internal class BSMServerOutputWorker : BackgroundService { + /// + /// Used to generate BSMs. + /// + private readonly BSMGenerator _bsmGenerator; + /// /// Used to log activity for the service. /// @@ -21,10 +26,12 @@ internal class BSMServerOutputWorker : BackgroundService /// /// The dependency injection constructor. /// + /// Used to generate BSMs. /// Used to log activity for the service. /// Used to manage TCP/IP communication. - public BSMServerOutputWorker(ILogger logger, IServer server) + public BSMServerOutputWorker(BSMGenerator bsmGenerator, ILogger logger, IServer server) { + _bsmGenerator = bsmGenerator; _logger = logger; _server = server; } @@ -38,8 +45,23 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { - if (_server.IsReady) - { + if (_server.IsReady && _server.ConnectionCount > 0) + { + //Generate a BSM & sends it to the remote clients. + try + { + BSMPDU pdu = new() + { + BSM = _bsmGenerator.Generate(), + }; + await _server.SendToAllAsync(pdu, stoppingToken); + _logger.LogInformation("The BSM server sent a BSM to the remote clients. {BSM}", pdu.BSM.ToTypeB()); + } + catch (Exception ex) + { + _logger.LogError(ex, "The BSM server failed to send the BSM to the remote clients."); + } + //Manage stale remote clients. List connectionIds = _server.GetStaleRemoteConnections(); @@ -56,26 +78,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) catch { } } } - - //Generate a BSM & sends it to the remote clients. - try - { - BSM.BSM bsm = new(); //Need to generate; should make a class to manage the generation. - BSMPDU pdu = new() - { - BSM = bsm, - }; - - await _server.SendToAllAsync(pdu, stoppingToken); - _logger.LogInformation("The BSM server sent a BSM to the remote clients. {BSM}", pdu.BSM.ToTypeB()); - } - catch (Exception ex) - { - _logger.LogError(ex, "The BSM server failed to send the BSM to the remote clients."); - } } - await Task.Delay(10_000, stoppingToken); + await Task.Delay(5_000, stoppingToken); } } } diff --git a/JMayer.Example.WindowsService/Program.cs b/JMayer.Example.WindowsService/Program.cs index e1b6b2d..2990674 100644 --- a/JMayer.Example.WindowsService/Program.cs +++ b/JMayer.Example.WindowsService/Program.cs @@ -1,5 +1,6 @@ using JMayer.Example.WindowsService; using JMayer.Example.WindowsService.BSM; +using JMayer.Net; using JMayer.Net.TcpIp; var builder = Host.CreateApplicationBuilder(args); @@ -8,8 +9,9 @@ options.ServiceName = "Example BSM Service"; }); -builder.Services.AddSingleton(); -builder.Services.AddSingleton(); +builder.Services.AddSingleton(); +builder.Services.AddSingleton(new TcpIpClient(new BSMParser())); +builder.Services.AddSingleton(new TcpIpServer(new BSMParser()) { ConnectionStaleMode = ConnectionStaleMode.LastSent, ConnectionTimeout = 60 }); builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); From 20d2159566abb8f6e5638c7e745856659c81017b Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:35:55 -0400 Subject: [PATCH 08/11] Switch client to use constant port number --- JMayer.Example.WindowsService/BSMClientWorker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JMayer.Example.WindowsService/BSMClientWorker.cs b/JMayer.Example.WindowsService/BSMClientWorker.cs index f6fdc15..a670454 100644 --- a/JMayer.Example.WindowsService/BSMClientWorker.cs +++ b/JMayer.Example.WindowsService/BSMClientWorker.cs @@ -43,7 +43,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { - await _client.ConnectAsync("127.0.0.1", 55555, stoppingToken); + await _client.ConnectAsync("127.0.0.1", BSMServerConnectionWorker.Port, stoppingToken); _logger.LogInformation("The client connected to the BSM server."); } catch (Exception ex) From 1a7dc6fdb33a3fd95471a24b0f3ebb39e0f02fec Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:43:00 -0400 Subject: [PATCH 09/11] Simplified variable name --- JMayer.Example.WindowsService/BSMServerOutputWorker.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/JMayer.Example.WindowsService/BSMServerOutputWorker.cs b/JMayer.Example.WindowsService/BSMServerOutputWorker.cs index 5afc531..7ccadd7 100644 --- a/JMayer.Example.WindowsService/BSMServerOutputWorker.cs +++ b/JMayer.Example.WindowsService/BSMServerOutputWorker.cs @@ -63,17 +63,17 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) } //Manage stale remote clients. - List connectionIds = _server.GetStaleRemoteConnections(); + List ids = _server.GetStaleRemoteConnections(); - if (connectionIds.Count > 0) + if (ids.Count > 0) { _logger.LogInformation("The BSM server detected stale remote clients; will attempt to disconnect."); - foreach (Guid connectionId in connectionIds) + foreach (Guid id in ids) { try { - _server.Disconnect(connectionId); + _server.Disconnect(id); } catch { } } From fe99cf2eec7760aa34aeaa7af1897f5fd79c2dd8 Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Sat, 17 Aug 2024 13:58:23 -0400 Subject: [PATCH 10/11] Adding Constants; reorganization of them --- .../BSM/BSMGenerator.cs | 56 ++++++++++++--- TestProject/Test/BSMGeneratorUnitTest.cs | 70 +++++++------------ 2 files changed, 73 insertions(+), 53 deletions(-) diff --git a/JMayer.Example.WindowsService/BSM/BSMGenerator.cs b/JMayer.Example.WindowsService/BSM/BSMGenerator.cs index 7b1f0a2..fd128b4 100644 --- a/JMayer.Example.WindowsService/BSM/BSMGenerator.cs +++ b/JMayer.Example.WindowsService/BSM/BSMGenerator.cs @@ -68,10 +68,10 @@ public class BSMGenerator /// private readonly IATAGenerator[] _iataGenerators = [ - new IATAGenerator() { AirlineAlphaNumericCode = "AA", AirlineNumericCode = "001", }, - new IATAGenerator() { AirlineAlphaNumericCode = "DL", AirlineNumericCode = "006", }, - new IATAGenerator() { AirlineAlphaNumericCode = "UA", AirlineNumericCode = "016", }, - new IATAGenerator() { AirlineAlphaNumericCode = "WN", AirlineNumericCode = "526", }, + new IATAGenerator() { AirlineAlphaNumericCode = AmericanAirlineAlphaCode, AirlineNumericCode = AmericanAirlinesNumericCode, }, + new IATAGenerator() { AirlineAlphaNumericCode = DeltaAlphaCode, AirlineNumericCode = DeltaNumericCode, }, + new IATAGenerator() { AirlineAlphaNumericCode = UnitedAirlinesAlphaCode, AirlineNumericCode = UnitedAirlinesNumericCode, }, + new IATAGenerator() { AirlineAlphaNumericCode = SouthwestAlpaCode, AirlineNumericCode = SouthwestNumericCode, }, ]; /// @@ -84,25 +84,45 @@ public class BSMGenerator /// private int _passengerCount = MinPassengerCount; + /// + /// The constant for the AA alphanumeric code. + /// + public const string AmericanAirlineAlphaCode = "AA"; + + /// + /// The constant for the AA numeric code. + /// + public const string AmericanAirlinesNumericCode = "001"; + + /// + /// The constant for the DL alphanumeric code. + /// + public const string DeltaAlphaCode = "DL"; + + /// + /// The constant for the DL numeric code. + /// + public const string DeltaNumericCode = "006"; + /// /// The constant for the .P given name. /// - private const string DotPGivenName = "PASSENGER"; + public const string DotPGivenName = "PASSENGER"; /// /// The constant for the .P surname. /// - private const string DotPSurName = "TEST"; + public const string DotPSurName = "TEST"; /// /// The constant for the .V airport code. /// - private const string DotVAirportCode = "MCO"; + public const string DotVAirportCode = "MCO"; /// /// The constant for the .V data dictionary version number. /// - private const int DotVDataDictionaryVersionNumber = 1; + public const int DotVDataDictionaryVersionNumber = 1; /// /// The constant for the maximum flight number. @@ -124,6 +144,26 @@ public class BSMGenerator /// public const int MinPassengerCount = 1; + /// + /// The constant for the WN alphanumeric code. + /// + public const string SouthwestAlpaCode = "WN"; + + /// + /// The constant for the WN numeric code. + /// + public const string SouthwestNumericCode = "526"; + + /// + /// The constant for the UA alphanumeric code. + /// + public const string UnitedAirlinesAlphaCode = "UA"; + + /// + /// The constant for the UA numeric code. + /// + public const string UnitedAirlinesNumericCode = "016"; + /// /// The default constructor. /// diff --git a/TestProject/Test/BSMGeneratorUnitTest.cs b/TestProject/Test/BSMGeneratorUnitTest.cs index 17461b5..c6f1ee0 100644 --- a/TestProject/Test/BSMGeneratorUnitTest.cs +++ b/TestProject/Test/BSMGeneratorUnitTest.cs @@ -7,26 +7,6 @@ namespace TestProject.Test; /// public class BSMGeneratorUnitTest { - /// - /// The constant for the .P given name. - /// - private const string DotPGivenName = "PASSENGER"; - - /// - /// The constant for the .P surname. - /// - private const string DotPSurName = "TEST"; - - /// - /// The constant for the .V airport code. - /// - private const string DotVAirportCode = "MCO"; - - /// - /// The constant for the .V data dictionary version number. - /// - private const int DotVDataDictionaryVersionNumber = 1; - /// /// The constant for the maximum number of airlines used by the BSM generator. /// @@ -50,14 +30,14 @@ public void VerifyAirlineRollover() //Verify the baggage tag details. Assert.NotNull(bsm.BaggageTagDetails); Assert.Single(bsm.BaggageTagDetails.BaggageTagNumbers); - Assert.Equal($"0001{(BSMGenerator.MaxPassengerCount + 1).ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); + Assert.Equal($"0{BSMGenerator.AmericanAirlinesNumericCode}{(BSMGenerator.MaxPassengerCount + 1).ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); //Verify the change of status. Assert.Equal(BSM.Add, bsm.ChangeOfStatus); //Verify the outbound flight. Assert.NotNull(bsm.OutboundFlight); - Assert.Equal("AA", bsm.OutboundFlight.Airline); + Assert.Equal(BSMGenerator.AmericanAirlineAlphaCode, bsm.OutboundFlight.Airline); Assert.NotEmpty(bsm.OutboundFlight.ClassOfTravel); //Class of Travel is randomly set so just make sure its set to something. Assert.NotEmpty(bsm.OutboundFlight.Destination); //Destination is randomly set so just make sure its set to something. Assert.Equal(DateTime.Today.ToDayMonthFormat(), bsm.OutboundFlight.FlightDate); @@ -66,14 +46,14 @@ public void VerifyAirlineRollover() //Verify the passenger name. Assert.NotNull(bsm.PassengerName); Assert.Single(bsm.PassengerName.GivenNames); - Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); - Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + Assert.Equal($"{BSMGenerator.DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(BSMGenerator.DotPSurName, bsm.PassengerName.SurName); //Verify the version supplementary data. Assert.NotNull(bsm.VersionSupplementaryData); - Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(BSMGenerator.DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); - Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + Assert.Equal(BSMGenerator.DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); } /// @@ -110,14 +90,14 @@ public void VerifyFlightNumberRollover() //Verify the passenger name. Assert.NotNull(bsm.PassengerName); Assert.Single(bsm.PassengerName.GivenNames); - Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); - Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + Assert.Equal($"{BSMGenerator.DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(BSMGenerator.DotPSurName, bsm.PassengerName.SurName); //Verify the version supplementary data. Assert.NotNull(bsm.VersionSupplementaryData); - Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(BSMGenerator.DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); - Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + Assert.Equal(BSMGenerator.DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); } /// @@ -132,14 +112,14 @@ public void VerifyGeneration() //Verify the baggage tag details. Assert.NotNull(bsm.BaggageTagDetails); Assert.Single(bsm.BaggageTagDetails.BaggageTagNumbers); - Assert.Equal($"0001{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); + Assert.Equal($"0{BSMGenerator.AmericanAirlinesNumericCode}{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); //Verify the change of status. Assert.Equal(BSM.Add, bsm.ChangeOfStatus); //Verify the outbound flight. Assert.NotNull(bsm.OutboundFlight); - Assert.Equal("AA", bsm.OutboundFlight.Airline); + Assert.Equal(BSMGenerator.AmericanAirlineAlphaCode, bsm.OutboundFlight.Airline); Assert.NotEmpty(bsm.OutboundFlight.ClassOfTravel); //Class of Travel is randomly set so just make sure its set to something. Assert.NotEmpty(bsm.OutboundFlight.Destination); //Destination is randomly set so just make sure its set to something. Assert.Equal(DateTime.Today.ToDayMonthFormat(), bsm.OutboundFlight.FlightDate); @@ -148,14 +128,14 @@ public void VerifyGeneration() //Verify the passenger name. Assert.NotNull(bsm.PassengerName); Assert.Single(bsm.PassengerName.GivenNames); - Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); - Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + Assert.Equal($"{BSMGenerator.DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(BSMGenerator.DotPSurName, bsm.PassengerName.SurName); //Verify the version supplementary data. Assert.NotNull(bsm.VersionSupplementaryData); - Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(BSMGenerator.DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); - Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + Assert.Equal(BSMGenerator.DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); } /// @@ -170,13 +150,13 @@ public void VerifyPassengerIncrementsByOne() BSM bsm3 = bsmGenerator.Generate(); Assert.NotNull(bsm1.PassengerName); - Assert.Equal($"{DotPGivenName}1", bsm1.PassengerName.GivenNames[0]); + Assert.Equal($"{BSMGenerator.DotPGivenName}1", bsm1.PassengerName.GivenNames[0]); Assert.NotNull(bsm2.PassengerName); - Assert.Equal($"{DotPGivenName}2", bsm2.PassengerName.GivenNames[0]); + Assert.Equal($"{BSMGenerator.DotPGivenName}2", bsm2.PassengerName.GivenNames[0]); Assert.NotNull(bsm3.PassengerName); - Assert.Equal($"{DotPGivenName}3", bsm3.PassengerName.GivenNames[0]); + Assert.Equal($"{BSMGenerator.DotPGivenName}3", bsm3.PassengerName.GivenNames[0]); } /// @@ -197,14 +177,14 @@ public void VerifyPassengerRollover() Assert.NotNull(bsm.BaggageTagDetails); Assert.Single(bsm.BaggageTagDetails.BaggageTagNumbers); //The sequence used by the BSM generator is 001, 006, 016, 526 so on the first passenger rollover, 006 will be used next. - Assert.Equal($"0006{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); + Assert.Equal($"0{BSMGenerator.DeltaNumericCode}{IATAGenerator.MinSequenceNumber.ToString().PadLeft(6, '0')}", bsm.BaggageTagDetails.BaggageTagNumbers[0]); //Verify the change of status. Assert.Equal(BSM.Add, bsm.ChangeOfStatus); //Verify the outbound flight. Assert.NotNull(bsm.OutboundFlight); - Assert.Equal("DL", bsm.OutboundFlight.Airline); //The sequence used by the BSM generator is AA, DL, UA, WN so on the first passenger rollover, DL will be used next. + Assert.Equal(BSMGenerator.DeltaAlphaCode, bsm.OutboundFlight.Airline); //The sequence used by the BSM generator is AA, DL, UA, WN so on the first passenger rollover, DL will be used next. Assert.NotEmpty(bsm.OutboundFlight.ClassOfTravel); //Class of Travel is randomly set so just make sure its set to something. Assert.NotEmpty(bsm.OutboundFlight.Destination); //Destination is randomly set so just make sure its set to something. Assert.Equal(DateTime.Today.ToDayMonthFormat(), bsm.OutboundFlight.FlightDate); @@ -213,13 +193,13 @@ public void VerifyPassengerRollover() //Verify the passenger name. Assert.NotNull(bsm.PassengerName); Assert.Single(bsm.PassengerName.GivenNames); - Assert.Equal($"{DotPGivenName}1", bsm.PassengerName.GivenNames[0]); - Assert.Equal(DotPSurName, bsm.PassengerName.SurName); + Assert.Equal($"{BSMGenerator.DotPGivenName}1", bsm.PassengerName.GivenNames[0]); + Assert.Equal(BSMGenerator.DotPSurName, bsm.PassengerName.SurName); //Verify the version supplementary data. Assert.NotNull(bsm.VersionSupplementaryData); - Assert.Equal(DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); + Assert.Equal(BSMGenerator.DotVAirportCode, bsm.VersionSupplementaryData.AirportCode); Assert.Equal(VersionSupplementaryData.LocalBaggageSourceIndicator, bsm.VersionSupplementaryData.BaggageSourceIndicator); - Assert.Equal(DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); + Assert.Equal(BSMGenerator.DotVDataDictionaryVersionNumber, bsm.VersionSupplementaryData.DataDictionaryVersionNumber); } } From dabf7356a36800fcba842f4c12e765e8dcb19118 Mon Sep 17 00:00:00 2001 From: jmayer913 <72579603+jmayer913@users.noreply.github.com> Date: Sat, 17 Aug 2024 14:02:27 -0400 Subject: [PATCH 11/11] Added github runner action --- workflows/mainworkflow.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 workflows/mainworkflow.yml diff --git a/workflows/mainworkflow.yml b/workflows/mainworkflow.yml new file mode 100644 index 0000000..c8c3466 --- /dev/null +++ b/workflows/mainworkflow.yml @@ -0,0 +1,29 @@ +# This workflow will build a .NET project +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net + +name: MainWorkflow + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + build: + + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: 8.0.x + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --configuration Release --no-restore + - name: Test + run: dotnet test --verbosity normal