Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.Commons.Tests/NativeAlarmSourceOverrideCsvParserTests.cs
T
Joseph Doherty 62fe1e685d feat(native-alarms): optional MesReportable column in the native-source override CSV import (ScadaBridge#102 item 4)
CLI import no longer has to reset MES overrides to inherit; a legacy 4-column file
still parses and the Central UI import keeps the stored flag for it.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
2026-11-09 15:33:31 -05:00

201 lines
7.0 KiB
C#

using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.Commons.Tests;
public class NativeAlarmSourceOverrideCsvParserTests
{
/// <summary>Verifies that a simple file parses into rows with no errors.</summary>
[Fact]
public void Parse_SimpleFile_ReturnsRowsNoErrors()
{
const string csv =
"SourceName,Connection,SourceReference,Filter\n" +
"Pressure,ConnA,ns=2;s=Pump.Alarm,Active\n" +
"Module.Temp,ConnB,ns=2;s=Temp,\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Errors);
Assert.Equal(2, result.Rows.Count);
Assert.Equal("Pressure", result.Rows[0].SourceName);
Assert.Equal("ConnA", result.Rows[0].Connection);
Assert.Equal("ns=2;s=Pump.Alarm", result.Rows[0].SourceReference);
Assert.Equal("Active", result.Rows[0].Filter);
Assert.Equal(2, result.Rows[0].LineNumber);
Assert.Equal("Module.Temp", result.Rows[1].SourceName);
Assert.Equal("ConnB", result.Rows[1].Connection);
Assert.Equal("ns=2;s=Temp", result.Rows[1].SourceReference);
Assert.Null(result.Rows[1].Filter);
Assert.Equal(3, result.Rows[1].LineNumber);
}
/// <summary>Verifies that blank override fields become null so the inherited value applies.</summary>
[Fact]
public void Parse_BlankOverrideFields_BecomeNullInherited()
{
const string csv =
"SourceName,Connection,SourceReference,Filter\n" +
"Pressure,,,\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Errors);
var row = Assert.Single(result.Rows);
Assert.Equal("Pressure", row.SourceName);
Assert.Null(row.Connection);
Assert.Null(row.SourceReference);
Assert.Null(row.Filter);
}
/// <summary>Verifies that header names are matched case-insensitively.</summary>
[Fact]
public void Parse_HeaderCaseInsensitive_Accepted()
{
const string csv =
"sourcename,CONNECTION,SourceReference,filter\n" +
"Pressure,ConnA,ref,\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Errors);
Assert.Single(result.Rows);
}
/// <summary>Verifies that a quoted field preserves an embedded comma.</summary>
[Fact]
public void Parse_QuotedFieldWithComma_PreservesEmbeddedComma()
{
const string csv =
"SourceName,Connection,SourceReference,Filter\n" +
"Pressure,ConnA,\"ns=2;s=a,b,c\",Active\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Errors);
var row = Assert.Single(result.Rows);
Assert.Equal("ns=2;s=a,b,c", row.SourceReference);
}
/// <summary>Verifies that a missing header returns a header error.</summary>
[Fact]
public void Parse_MissingHeader_ReturnsHeaderError()
{
const string csv = "Pressure,ConnA,ref,Active\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Rows);
var error = Assert.Single(result.Errors);
Assert.Contains("SourceName,Connection,SourceReference,Filter", error);
}
/// <summary>Verifies that a row with the wrong column count reports a per-line error and is skipped.</summary>
[Fact]
public void Parse_WrongColumnCount_ReportsPerLineErrorAndSkipsRow()
{
const string csv =
"SourceName,Connection,SourceReference,Filter\n" +
"Pressure,ConnA\n" +
"Temp,ConnB,ref,Active\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
var error = Assert.Single(result.Errors);
Assert.Contains("Line 2", error);
Assert.Contains("expected 4 columns", error);
// Valid rows still flow through.
var row = Assert.Single(result.Rows);
Assert.Equal("Temp", row.SourceName);
}
/// <summary>Verifies that a blank source name reports an error.</summary>
[Fact]
public void Parse_BlankSourceName_ReportsError()
{
const string csv =
"SourceName,Connection,SourceReference,Filter\n" +
",ConnA,ref,Active\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Rows);
var error = Assert.Single(result.Errors);
Assert.Contains("SourceName must not be blank", error);
}
/// <summary>Verifies that an unterminated quote reports an error.</summary>
[Fact]
public void Parse_UnterminatedQuote_ReportsError()
{
const string csv =
"SourceName,Connection,SourceReference,Filter\n" +
"Pressure,ConnA,\"ns=2;s=open,Active\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Rows);
Assert.Contains(result.Errors, e => e.Contains("Unterminated quoted field"));
}
/// <summary>Verifies that blank lines are skipped without an error.</summary>
[Fact]
public void Parse_BlankLinesSkippedWithoutError()
{
const string csv =
"SourceName,Connection,SourceReference,Filter\n" +
"\n" +
"Pressure,ConnA,ref,Active\n" +
" \n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Errors);
Assert.Single(result.Rows);
}
/// <summary>Gitea #102: the optional MesReportable column parses blank/true/false.</summary>
[Fact]
public void Parse_MesReportableColumn_ParsesTriState()
{
const string csv =
"SourceName,Connection,SourceReference,Filter,MesReportable\n" +
"A,,,,true\n" +
"B,,,,FALSE\n" +
"C,,,,\n";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(result.Errors);
Assert.All(result.Rows, r => Assert.True(r.HasMesColumn));
Assert.True(result.Rows[0].MesReportable);
Assert.False(result.Rows[1].MesReportable);
Assert.Null(result.Rows[2].MesReportable);
}
/// <summary>Gitea #102: a legacy 4-column file still parses and reports no MES column.</summary>
[Fact]
public void Parse_FourColumnFile_HasNoMesColumn()
{
var result = NativeAlarmSourceOverrideCsvParser.Parse(
"SourceName,Connection,SourceReference,Filter\nA,,,\n");
Assert.Empty(result.Errors);
var row = Assert.Single(result.Rows);
Assert.False(row.HasMesColumn);
Assert.Null(row.MesReportable);
}
/// <summary>Gitea #102: an unparseable MesReportable value is a line error.</summary>
[Fact]
public void Parse_InvalidMesReportable_ReportsLineError()
{
var result = NativeAlarmSourceOverrideCsvParser.Parse(
"SourceName,Connection,SourceReference,Filter,MesReportable\nA,,,,maybe\n");
Assert.Empty(result.Rows);
Assert.Contains(result.Errors, e => e.Contains("Line 2") && e.Contains("MesReportable"));
}
}