using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Driver.AbLegacy; namespace ZB.MOM.WW.OtOpcUa.Driver.AbLegacy.Tests; [Trait("Category", "Unit")] public sealed class AbLegacyAddressTests { [Theory] [InlineData("N7:0", "N", 7, 0, null, null)] [InlineData("N7:15", "N", 7, 15, null, null)] [InlineData("F8:5", "F", 8, 5, null, null)] [InlineData("B3:0/0", "B", 3, 0, 0, null)] [InlineData("B3:2/7", "B", 3, 2, 7, null)] [InlineData("ST9:0", "ST", 9, 0, null, null)] [InlineData("L9:3", "L", 9, 3, null, null)] [InlineData("I:0/0", "I", null, 0, 0, null)] [InlineData("O:1/2", "O", null, 1, 2, null)] [InlineData("S:1", "S", null, 1, null, null)] [InlineData("T4:0.ACC", "T", 4, 0, null, "ACC")] [InlineData("T4:0.PRE", "T", 4, 0, null, "PRE")] [InlineData("C5:2.CU", "C", 5, 2, null, "CU")] [InlineData("R6:0.LEN", "R", 6, 0, null, "LEN")] [InlineData("N7:0/3", "N", 7, 0, 3, null)] public void TryParse_accepts_valid_pccc_addresses(string input, string letter, int? file, int word, int? bit, string? sub) { var a = AbLegacyAddress.TryParse(input); a.ShouldNotBeNull(); a.FileLetter.ShouldBe(letter); a.FileNumber.ShouldBe(file); a.WordNumber.ShouldBe(word); a.BitIndex.ShouldBe(bit); a.SubElement.ShouldBe(sub); } [Theory] [InlineData(null)] [InlineData("")] [InlineData(" ")] [InlineData("N7")] // missing :word [InlineData(":0")] // missing file [InlineData("X7:0")] // unknown file letter [InlineData("N7:-1")] // negative word [InlineData("N7:abc")] // non-numeric word [InlineData("N7:0/-1")] // negative bit [InlineData("N7:0/32")] // bit out of range [InlineData("Nabc:0")] // non-numeric file number public void TryParse_rejects_invalid_forms(string? input) { AbLegacyAddress.TryParse(input).ShouldBeNull(); } [Theory] [InlineData("N7:0")] [InlineData("F8:5")] [InlineData("B3:0/0")] [InlineData("ST9:0")] [InlineData("T4:0.ACC")] [InlineData("I:0/0")] [InlineData("S:1")] public void ToLibplctagName_roundtrips(string input) { var a = AbLegacyAddress.TryParse(input); a.ShouldNotBeNull(); a.ToLibplctagName().ShouldBe(input); } // ---- Driver.AbLegacy-003: Parser tightening ---- [Theory] [InlineData("T4:0.ACC/2")] // sub-element + bit index — never valid in PCCC [InlineData("C5:0.PRE/3")] public void TryParse_rejects_subelement_plus_bitindex(string input) => AbLegacyAddress.TryParse(input).ShouldBeNull(); [Theory] [InlineData("I3:0")] // I is a system file — no file number allowed [InlineData("O2:1")] [InlineData("S2:1")] public void TryParse_rejects_file_number_on_IOS_files(string input) => AbLegacyAddress.TryParse(input).ShouldBeNull(); [Theory] [InlineData("B3:0.DN")] // B (bit) file has no structured elements [InlineData("N7:0.FOO")] // N (integer) file has no structured elements [InlineData("F8:0.ACC")] // F (float) file has no structured elements [InlineData("L9:0.PRE")] // L (long) file has no structured elements public void TryParse_rejects_subelement_on_non_structured_file(string input) => AbLegacyAddress.TryParse(input).ShouldBeNull(); [Theory] [InlineData("T4:0.ACC")] // T, C, R are the only structured-element files [InlineData("C5:0.PRE")] [InlineData("R6:0.LEN")] public void TryParse_accepts_subelement_only_on_TCR_files(string input) => AbLegacyAddress.TryParse(input).ShouldNotBeNull(); [Theory] [InlineData("I:0/0")] // I/O/S without file number are valid [InlineData("O:1/2")] [InlineData("S:1")] public void TryParse_accepts_IOS_without_file_number(string input) => AbLegacyAddress.TryParse(input).ShouldNotBeNull(); }