using ZB.MOM.WW.ScadaBridge.Commons.Types; namespace ZB.MOM.WW.ScadaBridge.Commons.Tests; public class NativeAlarmSourceOverrideCsvParserTests { [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); } [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); } [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); } [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); } [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); } [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); } [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); } [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")); } [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); } }