test(historian-gateway,live): assert FU-1 alarm SendEvent->ReadEvents round-trip (C4 fixed)
v2-ci / build (pull_request) Failing after 41s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (pull_request) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (pull_request) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (pull_request) Has been skipped

The gateway now populates Runtime.dbo.Events.Source_Object from the event
SourceName (gateway C4 fix: HistorianProtoMapper threads SourceName into the
`source_object` event property). So an ad-hoc alarm SendEvent is now source-
filterable on readback, and Alarm_SendEvent_then_ReadEvents asserts the round-
trip instead of skipping with the old "ad-hoc sends land without Source_Object"
reason. Poll window widened to 60s for the live event-view flush latency.

Live-proven at the gateway level (HistorianGateway EventSourceObjectProbeTests,
2026-06-27); this asserts the same round-trip through the OtOpcUa alarm writer +
data source adapters.

Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
Joseph Doherty
2026-06-27 16:48:16 -04:00
parent 53798bbf40
commit 7a94f7adf0
@@ -186,35 +186,30 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC
HistorianWriteOutcome.Ack, HistorianWriteOutcome.Ack,
"the alarm SendEvent must be acked — needs the gateway write scope (historian:write) and SendEvent path."); "the alarm SendEvent must be acked — needs the gateway write scope (historian:write) and SendEvent path.");
// Read the event back over a recent window for the source. The SQL event write can lag, so poll. // Read the event back over a recent window for the source. The SQL event write can lag (the
// live event view has variable flush latency), so poll generously.
await using var dataSource = _fx.CreateDataSource(); await using var dataSource = _fx.CreateDataSource();
IReadOnlyList<HistoricalEvent> events = Array.Empty<HistoricalEvent>(); IReadOnlyList<HistoricalEvent> events = Array.Empty<HistoricalEvent>();
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(15); var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(60);
do do
{ {
var read = await dataSource.ReadEventsAsync( var read = await dataSource.ReadEventsAsync(
source, eventUtc - TimeSpan.FromMinutes(5), DateTime.UtcNow + TimeSpan.FromMinutes(1), maxEvents: 0, ct); source, eventUtc - TimeSpan.FromMinutes(5), DateTime.UtcNow + TimeSpan.FromMinutes(1), maxEvents: 0, ct);
events = read.Events; events = read.Events;
if (events.Count > 0) break; if (events.Count > 0) break;
await Task.Delay(TimeSpan.FromSeconds(1), ct); await Task.Delay(TimeSpan.FromSeconds(2), ct);
} }
while (DateTime.UtcNow < deadline); while (DateTime.UtcNow < deadline);
// The SendEvent itself is the OtOpcUa contract and is asserted above. The source-filtered // The source-filtered READBACK of OtOpcUa's own just-sent alarm now round-trips: the gateway
// READBACK of a just-sent event is a historian/gateway property, not an OtOpcUa one: verified // threads the wire event's SourceName into the `source_object` event property, so an ad-hoc
// live, the gateway's SQL event reader works and source-filtering works for events that carry a // SendEvent is recorded in dbo.Events WITH a Source_Object and a source-filtered ReadEvents
// Source_Object (real Galaxy-sourced events read back by source correctly), but an ad-hoc // finds it (gateway C4 fix, 2026-06-27; pending.md C4 / histsdk #3). Previously this branch
// SendEvent is recorded in dbo.Events WITHOUT a Source_Object — so a source-filtered read of a // skipped because ad-hoc sends landed with Source_Object=NULL; that limitation is now closed,
// freshly-sent event finds nothing. Reading existing Galaxy alarm/event history by source works; // so the round-trip is asserted.
// round-tripping OtOpcUa's OWN sends by source needs the gateway's SendEvent to populate the events.ShouldNotBeEmpty(
// event source. So when no event comes back, skip with that reason rather than failing. $"a source-filtered ReadEvents must return the just-sent alarm for source '{source}': the gateway now " +
if (events.Count == 0) "populates Source_Object from the event SourceName (C4 fix), so OtOpcUa's own alarm sends round-trip.");
{
Assert.Skip(
$"Send acked, but a source-filtered ReadEvents returned 0 for source '{source}'. The SQL event " +
"reader works (Galaxy-sourced events read back by source); an ad-hoc SendEvent is stored without " +
"a Source_Object, so its source-filtered readback is empty until the gateway populates the event source.");
}
var exactMatch = events.Any(e => string.Equals(e.EventId, alarmId, StringComparison.Ordinal)); var exactMatch = events.Any(e => string.Equals(e.EventId, alarmId, StringComparison.Ordinal));
TestContext.Current.SendDiagnosticMessage( TestContext.Current.SendDiagnosticMessage(