Strengthen live event-read test: assert well-formed parsed events

ReadEventsAsync verified to return real, parsed events against the local 2020
server (e.g. User.Write with 18 properties) — the row parser (HistorianEventRowProtocol
v9) is wired and works. The prior test only asserted NotNull with a stale "row
format not yet decoded" comment.

- Renamed to ReadEventsAsync_AgainstLocalHistorian_ReturnsWellFormedEvents.
- Widened the window to 30 days (robust against a quiet recent window).
- Asserts NotEmpty + per-event well-formedness (non-empty Type, non-null
  Properties, EventTimeUtc within the queried window) — matching the ReadRawAsync
  test's NotEmpty style.
- Documents the known limitation: enumeration stops at the first benign
  `type=4 code=85` soft-terminal, so this verifies parsing correctness rather than
  exhaustive retrieval (draining all rows needs the code-85 decode, a capture task).

Passes live (1 event over 30 days). Non-live unit suite unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-06-19 15:12:41 -04:00
parent cf5a66e046
commit 7d5aeaeb06
@@ -211,7 +211,7 @@ public sealed class HistorianClientIntegrationTests
} }
[Fact] [Fact]
public async Task ReadEventsAsync_AgainstLocalHistorian_DoesNotThrow() public async Task ReadEventsAsync_AgainstLocalHistorian_ReturnsWellFormedEvents()
{ {
string? host = Environment.GetEnvironmentVariable("HISTORIAN_HOST"); string? host = Environment.GetEnvironmentVariable("HISTORIAN_HOST");
if (string.IsNullOrWhiteSpace(host) || !string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase) || !OperatingSystem.IsWindows()) if (string.IsNullOrWhiteSpace(host) || !string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase) || !OperatingSystem.IsWindows())
@@ -227,18 +227,28 @@ public sealed class HistorianClientIntegrationTests
}); });
DateTime endUtc = DateTime.UtcNow; DateTime endUtc = DateTime.UtcNow;
DateTime startUtc = endUtc - TimeSpan.FromDays(7); DateTime startUtc = endUtc - TimeSpan.FromDays(30);
// The event-row WCF wire format is not yet decoded; this test verifies the chain // The full chain (ValCl + Open2 + Retr.IsOriginalAllowed + Retr.StartEventQuery +
// (ValCl + Open2 + Retr.IsOriginalAllowed + Retr.StartEventQuery) reaches the server // GetNextEventQueryResultBuffer + HistorianEventRowProtocol.Parse) returns real, parsed
// without throwing. An empty event list is acceptable until row parsing is wired. // events. Requires the local store to hold events in the window — System-Platform
// alarm/user-write events are present on a working Historian. NOTE: enumeration currently
// stops at the first benign `type=4 code=85` soft-terminal, so this verifies parsing
// correctness rather than exhaustive retrieval (decoding code 85 to drain all rows is a
// separate capture task).
List<AVEVA.Historian.Client.Models.HistorianEvent> events = []; List<AVEVA.Historian.Client.Models.HistorianEvent> events = [];
await foreach (AVEVA.Historian.Client.Models.HistorianEvent evt in client.ReadEventsAsync(startUtc, endUtc, CancellationToken.None)) await foreach (AVEVA.Historian.Client.Models.HistorianEvent evt in client.ReadEventsAsync(startUtc, endUtc, CancellationToken.None))
{ {
events.Add(evt); events.Add(evt);
} }
Assert.NotNull(events); Assert.NotEmpty(events);
Assert.All(events, evt =>
{
Assert.False(string.IsNullOrWhiteSpace(evt.Type)); // e.g. "User.Write", "Alarm.Set"
Assert.NotNull(evt.Properties);
Assert.InRange(evt.EventTimeUtc, startUtc - TimeSpan.FromDays(1), endUtc + TimeSpan.FromDays(1));
});
} }
[Fact] [Fact]