Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.AdminUI.Tests/Uns/EquipmentCsvParserTests.cs
T
Joseph Doherty 746267c2a2 fix(adminui): equipment CSV import reads RFC 4180 via CsvParser (R04 S6)
ImportEquipmentModal split each line on commas, so a quoted Manufacturer with
a comma silently shifted every later column. EquipmentCsvParser reads through
the shared Commons CsvParser (the raw-tag importer's authority): quoted
fields, doubled quotes, CRLF and blank lines parse; malformed quoting is an
error, not a guess. Header/shape messages are unchanged.

Refs #745
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
2026-10-22 17:56:26 -04:00

63 lines
2.2 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.AdminUI.Uns;
namespace ZB.MOM.WW.OtOpcUa.AdminUI.Tests.Uns;
/// <summary>R04 S6: the equipment CSV import reads RFC 4180 through the shared CsvParser.</summary>
[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(',')");
}
}