From da09be3127716246c194c569614147953e0b431c Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 25 Jun 2026 10:33:44 -0400 Subject: [PATCH] feat: add GalaxyAlarmAttributeRow + MapAlarmRow Ports the alarm-attribute row type and its internal mapping helper from mxaccessgw (ZB.MOM.WW.MxGateway.Server.Galaxy) into the shared lib as the first step of the galaxy-0.2.0-mxaccessgw-gaps feature branch. Adds InternalsVisibleTo so the test project can exercise MapAlarmRow without a database. Five unit tests all pass; zero-warning build. --- .../GalaxyAlarmAttributeRow.cs | 48 +++++++++++++ .../GalaxyRepository.cs | 31 +++++++++ .../ZB.MOM.WW.GalaxyRepository.csproj | 4 ++ .../GalaxyAlarmAttributeMappingTests.cs | 68 +++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyAlarmAttributeRow.cs create mode 100644 ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/GalaxyAlarmAttributeMappingTests.cs diff --git a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyAlarmAttributeRow.cs b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyAlarmAttributeRow.cs new file mode 100644 index 0000000..ae00764 --- /dev/null +++ b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyAlarmAttributeRow.cs @@ -0,0 +1,48 @@ +namespace ZB.MOM.WW.GalaxyRepository; + +/// +/// One alarm-bearing attribute discovered by GalaxyRepository.GetAlarmAttributesAsync: +/// an attribute whose owning object configures an AlarmExtension primitive (the +/// same is_alarm detection used by +/// ). +/// Used to build the subtag-fallback watch-list. +/// +public sealed class GalaxyAlarmAttributeRow +{ + /// + /// Gets the alarm-bearing attribute reference (e.g. Tank01.Level.HiHi), + /// matching the full_tag_reference projection of + /// . + /// + public string FullTagReference { get; init; } = string.Empty; + + /// + /// Gets the owning object reference (e.g. Tank01). This is the Galaxy + /// tag_name — the segment that precedes the first attribute dot in + /// . + /// + public string SourceObjectReference { get; init; } = string.Empty; + + /// + /// Gets the owning object's Galaxy area (e.g. TestArea) — the alarm group. + /// + /// Resolved via gobject.area_gobject_id in AlarmAttributesSql. The + /// watch-list resolver composes the canonical Galaxy!{area}.{reference} from + /// this so the synthesized reference's group matches the native alarmmgr (wnwrap) + /// for reference parity. May be when the object has no + /// area; the resolver then falls back to the configured area. + /// + /// + public string Area { get; init; } = string.Empty; + + /// + /// Gets the writable ack-comment attribute address. + /// + /// The Galaxy Repository schema does not expose an ack-comment subtag address + /// directly, so this is always here. The watch-list + /// resolver (a later task) composes the concrete address from configuration plus + /// / . + /// + /// + public string AckCommentSubtag { get; init; } = string.Empty; +} diff --git a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs index eb6224c..9a8d34b 100644 --- a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs +++ b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/GalaxyRepository.cs @@ -114,6 +114,37 @@ public sealed class GalaxyRepository(GalaxyRepositoryOptions options) : IGalaxyR return rows; } + /// + /// Maps the SQL columns projected by AlarmAttributesSql onto a + /// . + /// + /// is the alarm-bearing attribute reference (e.g. + /// Tank01.Level.HiHi), matching the same full_tag_reference projection + /// of produces. + /// is left empty here; the + /// schema does not expose an ack-comment address and the watch-list resolver + /// composes it later. + /// + /// is the owning object's real Galaxy area (its alarm + /// group), resolved via gobject.area_gobject_id; the watch-list resolver + /// composes the canonical reference from it so the synthesized reference's group + /// matches what the native alarmmgr (wnwrap) emits. + /// Exposed internally so the derivation can be unit-tested without a database. + /// + /// The alarm-bearing attribute reference. + /// The owning object reference (tag name). + /// The owning object's Galaxy area (the alarm group). + internal static GalaxyAlarmAttributeRow MapAlarmRow( + string fullTagReference, + string sourceObjectReference, + string area) => new() + { + FullTagReference = fullTagReference, + SourceObjectReference = sourceObjectReference, + Area = area, + AckCommentSubtag = string.Empty, + }; + // Area objects (category 13) are returned even when undeployed (deployed_package_id = 0): // they are organizational/model nodes that group deployed objects, so excluding them // orphans every area whose containing area is not itself deployed. All non-area objects diff --git a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/ZB.MOM.WW.GalaxyRepository.csproj b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/ZB.MOM.WW.GalaxyRepository.csproj index 1972545..e560bd9 100644 --- a/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/ZB.MOM.WW.GalaxyRepository.csproj +++ b/ZB.MOM.WW.GalaxyRepository/src/ZB.MOM.WW.GalaxyRepository/ZB.MOM.WW.GalaxyRepository.csproj @@ -27,4 +27,8 @@ + + + + diff --git a/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/GalaxyAlarmAttributeMappingTests.cs b/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/GalaxyAlarmAttributeMappingTests.cs new file mode 100644 index 0000000..32b52d3 --- /dev/null +++ b/ZB.MOM.WW.GalaxyRepository/tests/ZB.MOM.WW.GalaxyRepository.Tests/GalaxyAlarmAttributeMappingTests.cs @@ -0,0 +1,68 @@ +using ZB.MOM.WW.GalaxyRepository; + +namespace ZB.MOM.WW.GalaxyRepository.Tests; + +/// +/// Pure mapper tests for . These assert the +/// FullTagReference / SourceObjectReference derivation produced by +/// AlarmAttributesSql without touching a database: the SQL projects +/// tag_name as the source object and tag_name + '.' + attribute_name as +/// the full reference, exactly as AttributesSql does. +/// +public sealed class GalaxyAlarmAttributeMappingTests +{ + /// Verifies the mapper copies all projected columns onto the row. + [Fact] + public void MapAlarmRow_CopiesProjectedColumns() + { + GalaxyAlarmAttributeRow row = GalaxyRepository.MapAlarmRow( + fullTagReference: "Tank01.Level.HiHi", + sourceObjectReference: "Tank01", + area: "TestArea"); + + Assert.Equal("Tank01.Level.HiHi", row.FullTagReference); + Assert.Equal("Tank01", row.SourceObjectReference); + Assert.Equal("TestArea", row.Area); + } + + /// + /// Verifies is always empty: + /// the schema does not expose an ack-comment address, so the watch-list resolver + /// composes it later from configuration. + /// + [Fact] + public void MapAlarmRow_LeavesAckCommentSubtagEmpty() + { + GalaxyAlarmAttributeRow row = GalaxyRepository.MapAlarmRow( + fullTagReference: "Tank01.Level.HiHi", + sourceObjectReference: "Tank01", + area: "TestArea"); + + Assert.Equal(string.Empty, row.AckCommentSubtag); + } + + /// + /// Verifies the SourceObjectReference is the owning object (the SQL tag_name), + /// i.e. the segment that precedes the first attribute dot in the full reference, even + /// when the attribute itself is a multi-segment extension path. + /// + [Theory] + [InlineData("Tank01", "Level.HiHi", "Tank01.Level.HiHi")] + [InlineData("Pump_001", "Speed", "Pump_001.Speed")] + [InlineData("TestAlarm001", "Alarm001", "TestAlarm001.Alarm001")] + public void MapAlarmRow_SourceObjectIsSegmentBeforeFirstAttributeDot( + string tagName, + string attributeName, + string expectedFullReference) + { + // Mirror the AlarmAttributesSql projection: full_tag_reference = tag_name + '.' + attribute_name. + string fullTagReference = tagName + "." + attributeName; + + GalaxyAlarmAttributeRow row = GalaxyRepository.MapAlarmRow(fullTagReference, tagName, area: "TestArea"); + + Assert.Equal(expectedFullReference, row.FullTagReference); + Assert.Equal(tagName, row.SourceObjectReference); + Assert.Equal("TestArea", row.Area); + Assert.Equal(row.FullTagReference, row.SourceObjectReference + "." + attributeName); + } +}