feat(commons): quote-aware OverrideCsvParser (T16 CSV)
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
using ZB.MOM.WW.ScadaBridge.Commons.Types;
|
||||
|
||||
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests;
|
||||
|
||||
public class OverrideCsvParserTests
|
||||
{
|
||||
[Fact]
|
||||
public void Parse_SimpleThreeColumnFile_ReturnsTwoRowsNoErrors()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\nSetpoint,42,Int32\nName,Pump A,\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
Assert.Equal(2, result.Rows.Count);
|
||||
|
||||
Assert.Equal("Setpoint", result.Rows[0].AttributeName);
|
||||
Assert.Equal("42", result.Rows[0].Value);
|
||||
Assert.Equal("Int32", result.Rows[0].ElementType);
|
||||
Assert.Equal(2, result.Rows[0].LineNumber);
|
||||
|
||||
Assert.Equal("Name", result.Rows[1].AttributeName);
|
||||
Assert.Equal("Pump A", result.Rows[1].Value);
|
||||
Assert.Null(result.Rows[1].ElementType);
|
||||
Assert.Equal(3, result.Rows[1].LineNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_TwoColumnFileWithoutElementType_RowsHaveNullElementType()
|
||||
{
|
||||
const string csv = "AttributeName,Value\nSetpoint,42\nName,Pump A\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
Assert.Equal(2, result.Rows.Count);
|
||||
Assert.All(result.Rows, r => Assert.Null(r.ElementType));
|
||||
Assert.Equal("42", result.Rows[0].Value);
|
||||
Assert.Equal("Pump A", result.Rows[1].Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_QuotedValueWithComma_PreservesEmbeddedComma()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\nName,\"a,b,c\",\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
var row = Assert.Single(result.Rows);
|
||||
Assert.Equal("Name", row.AttributeName);
|
||||
Assert.Equal("a,b,c", row.Value);
|
||||
Assert.Null(row.ElementType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_DoubledQuoteEscape_UnescapesToSingleQuote()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\nName,\"he said \"\"hi\"\"\",\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
var row = Assert.Single(result.Rows);
|
||||
Assert.Equal("he said \"hi\"", row.Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_EmptyValueField_YieldsNullValue()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\nSetpoint,,\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
var row = Assert.Single(result.Rows);
|
||||
Assert.Equal("Setpoint", row.AttributeName);
|
||||
Assert.Null(row.Value);
|
||||
Assert.Null(row.ElementType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_RowWithTooFewColumns_ProducesLineNumberedErrorAndExcludesRow()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\nSetpoint\nName,Pump A,\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
// Bad row on line 2 excluded; good row on line 3 retained.
|
||||
var row = Assert.Single(result.Rows);
|
||||
Assert.Equal("Name", row.AttributeName);
|
||||
Assert.Equal(3, row.LineNumber);
|
||||
|
||||
var error = Assert.Single(result.Errors);
|
||||
Assert.Contains("2", error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BlankAttributeName_ProducesLineNumberedError()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\n,42,Int32\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Rows);
|
||||
var error = Assert.Single(result.Errors);
|
||||
Assert.Contains("2", error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_BlankLines_AreSkippedWithoutError()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\n\nSetpoint,42,Int32\n \nName,Pump A,\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
Assert.Equal(2, result.Rows.Count);
|
||||
// LineNumber reflects the true source line (blank line 2 skipped).
|
||||
Assert.Equal(3, result.Rows[0].LineNumber);
|
||||
Assert.Equal(5, result.Rows[1].LineNumber);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_MissingHeader_ReturnsZeroRowsAndHeaderError()
|
||||
{
|
||||
const string csv = "Setpoint,42,Int32\nName,Pump A,\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Rows);
|
||||
var error = Assert.Single(result.Errors);
|
||||
Assert.Contains("header", error, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_HeaderIsCaseInsensitiveAndTrimsWhitespace()
|
||||
{
|
||||
const string csv = "attributename, value , elementtype\nSetpoint,42,Int32\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
var row = Assert.Single(result.Rows);
|
||||
Assert.Equal("Setpoint", row.AttributeName);
|
||||
Assert.Equal("42", row.Value);
|
||||
Assert.Equal("Int32", row.ElementType);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_EmptyInput_ReturnsHeaderError()
|
||||
{
|
||||
var result = OverrideCsvParser.Parse(string.Empty);
|
||||
|
||||
Assert.Empty(result.Rows);
|
||||
Assert.Single(result.Errors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Parse_UnquotedWhitespace_IsTrimmedButQuotedWhitespacePreserved()
|
||||
{
|
||||
const string csv = "AttributeName,Value,ElementType\n Setpoint , 42 ,Int32\nName,\" spaced \",\n";
|
||||
|
||||
var result = OverrideCsvParser.Parse(csv);
|
||||
|
||||
Assert.Empty(result.Errors);
|
||||
Assert.Equal(2, result.Rows.Count);
|
||||
Assert.Equal("Setpoint", result.Rows[0].AttributeName);
|
||||
Assert.Equal("42", result.Rows[0].Value);
|
||||
Assert.Equal(" spaced ", result.Rows[1].Value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user