Files
ScadaBridge/tests/ZB.MOM.WW.ScadaBridge.CLI.Tests/ImportNativeAlarmSourceOverridesTests.cs
T
Joseph Doherty f480a56737 feat(instance): native-alarm-source-override CSV bulk import (deferred #12) + doc fixes
Closes the operator parity gap the deferred-work register tracked as #12: native
alarm source overrides could only be set one-at-a-time, while attribute overrides
had a CSV bulk path. Adds an all-or-nothing CSV import for native sources.

- Commons: extract the RFC-4180 line splitter into a shared CsvLineSplitter
  (refactor OverrideCsvParser onto it — no behavior change, pinned by its tests);
  new NativeAlarmSourceOverrideCsvParser (header SourceName,Connection,
  SourceReference,Filter; blank = inherited).
- Commons: NativeAlarmSourceOverrideEntry + bulk SetInstanceNativeAlarmSource-
  OverridesCommand (auto-registered via the reflection command registry).
- ManagementService: Deployer-gated handler — flattens once, validates every
  source resolves + is unlocked + no duplicates up front, then upserts the whole
  batch under a single SaveChanges (true all-or-nothing txn). Added to the frozen
  authorization matrix; dispatch-coverage guard passes.
- CLI: `instance native-alarm-source import --instance-id --file` (parity with
  `instance import-overrides`) + README.
- Tests: native parser (Commons), CLI parse/entry mapping, 3 bulk-handler tests
  (happy path single-commit, locked-source reject, unresolved-source reject).

Also corrects a stale XML-doc line in ScriptRuntimeContext (WaitForAttribute
quality-gated mode is shipped, not "planned") and updates the deferred-work
register: marks #7/#13/#15/#16/#20 verified-resolved, #12 as CLI/API-shipped with
only the Central UI upload affordance still pending.

Claude-Session: https://claude.ai/code/session_01MtdgwpEeCUn6cUA5f1LMPj
2026-07-10 08:52:12 -04:00

69 lines
2.7 KiB
C#

using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management;
using ZB.MOM.WW.ScadaBridge.Commons.Types;
namespace ZB.MOM.WW.ScadaBridge.CLI.Tests;
/// <summary>
/// Verifies the CSV parsing + entry-mapping behaviour that backs
/// <c>instance native-alarm-source import</c>: parse errors block the apply step;
/// valid CSV produces the <see cref="NativeAlarmSourceOverrideEntry"/> list the
/// <see cref="SetInstanceNativeAlarmSourceOverridesCommand"/> consumes.
/// </summary>
public class ImportNativeAlarmSourceOverridesTests
{
// ── error path ───────────────────────────────────────────────────────────
[Fact]
public void CsvWithMissingHeader_ProducesErrors()
{
var result = NativeAlarmSourceOverrideCsvParser.Parse("Pressure,ConnA,ref,Active");
Assert.NotEmpty(result.Errors);
Assert.Empty(result.Rows);
}
[Fact]
public void CsvWithBlankSourceName_ProducesError()
{
const string csv = "SourceName,Connection,SourceReference,Filter\n,ConnA,ref,Active";
var result = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.NotEmpty(result.Errors);
Assert.Empty(result.Rows);
}
// ── happy path ───────────────────────────────────────────────────────────
[Fact]
public void ValidCsv_ProducesCorrectEntryList()
{
const string csv = """
SourceName,Connection,SourceReference,Filter
Pressure,ConnA,ns=2;s=Pump,Active
Temp,,,
""";
var parseResult = NativeAlarmSourceOverrideCsvParser.Parse(csv);
Assert.Empty(parseResult.Errors);
Assert.Equal(2, parseResult.Rows.Count);
// Build the same entry list that the CLI import action produces.
var entries = parseResult.Rows
.Select(r => new NativeAlarmSourceOverrideEntry(
r.SourceName, r.Connection, r.SourceReference, r.Filter))
.ToList();
Assert.Equal("Pressure", entries[0].SourceCanonicalName);
Assert.Equal("ConnA", entries[0].ConnectionNameOverride);
Assert.Equal("ns=2;s=Pump", entries[0].SourceReferenceOverride);
Assert.Equal("Active", entries[0].ConditionFilterOverride);
// All-blank override fields → inherited (null) on every field.
Assert.Equal("Temp", entries[1].SourceCanonicalName);
Assert.Null(entries[1].ConnectionNameOverride);
Assert.Null(entries[1].SourceReferenceOverride);
Assert.Null(entries[1].ConditionFilterOverride);
}
}