using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.AdminUI.Uns; namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns; /// R04 S6: the equipment CSV import reads RFC 4180 through the shared CsvParser. [Trait("Category", "Unit")] public sealed class EquipmentCsvParserTests { private const string Header = "Name,MachineCode,UnsLineId,ZTag,SAPID,Manufacturer,Model"; [Fact] public void A_quoted_comma_stays_in_its_column() { var (rows, error) = EquipmentCsvParser.Parse( Header + "\nPress 1,MC-1,LINE-1,Z1,S1,\"Acme, Inc.\",X200\n"); error.ShouldBeNull(); rows!.Single().Manufacturer.ShouldBe("Acme, Inc."); // The column after the quoted comma did not shift. rows!.Single().Model.ShouldBe("X200"); } [Fact] public void Doubled_quotes_crlf_and_blank_lines_parse() { var (rows, error) = EquipmentCsvParser.Parse( Header + "\r\n\r\n\"Press \"\"A\"\"\",MC-1,LINE-1,,,,\r\nPress B,MC-2,LINE-1\r\n"); error.ShouldBeNull(); rows!.Select(r => r.Name).ShouldBe(["Press \"A\"", "Press B"]); rows![0].ZTag.ShouldBeNull(); rows[1].Manufacturer.ShouldBeNull(); } [Fact] public void Header_and_shape_errors_are_reported() { EquipmentCsvParser.Parse("").Error.ShouldBe("CSV is empty."); EquipmentCsvParser.Parse(Header).Error.ShouldBe("Need a header row and at least one data row."); EquipmentCsvParser.Parse("Name,Code,UnsLineId\na,b,c").Error!.ShouldContain("Header column #2 must be 'MachineCode'"); EquipmentCsvParser.Parse(Header + "\nonly,two").Error!.ShouldContain("Row 1: too few columns"); } [Fact] public void Malformed_quoting_is_an_error_not_a_guess() { var (rows, error) = EquipmentCsvParser.Parse(Header + "\nPress,MC-1,LINE-1,\"unterminated\n"); rows.ShouldBeNull(); error!.ShouldStartWith("The CSV is malformed:"); } [Fact] public void Modal_delegates_to_the_parser() { var src = AdminUiSource.Read("Components", "Shared", "Uns", "ImportEquipmentModal.razor"); src.ShouldContain("EquipmentCsvParser.Parse(csv)"); src.ShouldNotContain(".Split(',')"); } }