using Shouldly; using Xunit; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.ParityTests; /// /// PR 5.6 — History-read parity. Phase-1 routing lifted history off the /// per-driver path onto the server-owned /// HistoryRouter + WonderwareHistorianBootstrap; neither /// Galaxy backend implements directly. So /// the parity surface here is the *routing decision*: both backends must /// identify the same set of historized attributes and produce the same /// full-reference for each, so HistoryRouter routes reads identically. /// [Trait("Category", "ParityE2E")] [Collection(nameof(ParityCollection))] public sealed class HistoryReadParityTests { private readonly ParityHarness _h; public HistoryReadParityTests(ParityHarness h) => _h = h; [Fact] public async Task Discover_emits_same_historized_attribute_set_for_both_backends() { _h.RequireBoth(); var snapshots = await _h.RunOnAvailableAsync(async (driver, ct) => { var b = new RecordingAddressSpaceBuilder(); await ((ITagDiscovery)driver).DiscoverAsync(b, ct); return b.Variables .Where(v => v.AttributeInfo.IsHistorized) .Select(v => v.AttributeInfo.FullName) .ToHashSet(StringComparer.OrdinalIgnoreCase); }, CancellationToken.None); var legacy = snapshots[ParityHarness.Backend.LegacyHost]; var mxgw = snapshots[ParityHarness.Backend.MxGateway]; if (legacy.Count == 0) { Assert.Skip("dev Galaxy has no historized attributes — history routing parity unverified for this rig"); } legacy.Except(mxgw, StringComparer.OrdinalIgnoreCase).ShouldBeEmpty( "every historized attribute discovered by the legacy backend must appear in the mxgw backend"); mxgw.Except(legacy, StringComparer.OrdinalIgnoreCase).ShouldBeEmpty( "every historized attribute discovered by the mxgw backend must appear in the legacy backend"); } [Fact] public async Task The_new_Galaxy_backend_does_not_implement_IHistoryProvider_directly() { // Pinning the architectural decision from Phase 1 (PR 1.3): per-driver // IHistoryProvider was retired in favor of the server-owned HistoryRouter // for the *new* in-process GalaxyDriver. The legacy GalaxyProxyDriver // still surfaces IHistoryProvider for back-compat with the legacy server // bootstrap path (it's an accepted delta — the legacy driver retires in // PR 7.2 alongside the rest of the legacy projects). The architectural // pin we want to enforce is "the *new* path doesn't regress to per-driver // history". _h.RequireBoth(); (_h.MxGatewayDriver as IHistoryProvider).ShouldBeNull( "in-process GalaxyDriver must not surface IHistoryProvider — history routes through HistoryRouter"); await Task.CompletedTask; } }