From 7a94f7adf01ef6d92fb4e62def142dc36e901b52 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Sat, 27 Jun 2026 16:48:16 -0400 Subject: [PATCH] test(historian-gateway,live): assert FU-1 alarm SendEvent->ReadEvents round-trip (C4 fixed) 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 --- .../Live/GatewayLiveIntegrationTests.cs | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs index fb9f06a3..8479aaa1 100644 --- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs +++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Gateway.Tests/Live/GatewayLiveIntegrationTests.cs @@ -186,35 +186,30 @@ public sealed class GatewayLiveIntegrationTests(GatewayLiveFixture fixture) : IC HistorianWriteOutcome.Ack, "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(); IReadOnlyList events = Array.Empty(); - var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(15); + var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(60); do { var read = await dataSource.ReadEventsAsync( source, eventUtc - TimeSpan.FromMinutes(5), DateTime.UtcNow + TimeSpan.FromMinutes(1), maxEvents: 0, ct); events = read.Events; if (events.Count > 0) break; - await Task.Delay(TimeSpan.FromSeconds(1), ct); + await Task.Delay(TimeSpan.FromSeconds(2), ct); } while (DateTime.UtcNow < deadline); - // The SendEvent itself is the OtOpcUa contract and is asserted above. The source-filtered - // READBACK of a just-sent event is a historian/gateway property, not an OtOpcUa one: verified - // live, the gateway's SQL event reader works and source-filtering works for events that carry a - // Source_Object (real Galaxy-sourced events read back by source correctly), but an ad-hoc - // SendEvent is recorded in dbo.Events WITHOUT a Source_Object — so a source-filtered read of a - // freshly-sent event finds nothing. Reading existing Galaxy alarm/event history by source works; - // round-tripping OtOpcUa's OWN sends by source needs the gateway's SendEvent to populate the - // event source. So when no event comes back, skip with that reason rather than failing. - if (events.Count == 0) - { - 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."); - } + // The source-filtered READBACK of OtOpcUa's own just-sent alarm now round-trips: the gateway + // threads the wire event's SourceName into the `source_object` event property, so an ad-hoc + // SendEvent is recorded in dbo.Events WITH a Source_Object and a source-filtered ReadEvents + // finds it (gateway C4 fix, 2026-06-27; pending.md C4 / histsdk #3). Previously this branch + // skipped because ad-hoc sends landed with Source_Object=NULL; that limitation is now closed, + // so the round-trip is asserted. + events.ShouldNotBeEmpty( + $"a source-filtered ReadEvents must return the just-sent alarm for source '{source}': the gateway now " + + "populates Source_Object from the event SourceName (C4 fix), so OtOpcUa's own alarm sends round-trip."); var exactMatch = events.Any(e => string.Equals(e.EventId, alarmId, StringComparison.Ordinal)); TestContext.Current.SendDiagnosticMessage(