using System;
using System.Reflection;
using ArchestrA;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Backend;
namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.Backend;
///
/// Driver.Historian.Wonderware-005 regression tests for .
/// The active-node strings and the connection-open booleans were published under different
/// locks, so a snapshot could observe an internally inconsistent pairing (open with no node,
/// or closed with a non-null node). The fix derives the open booleans from the same field
/// that is published under the same lock so the snapshot is self-consistent by construction.
///
[Trait("Category", "Unit")]
public sealed class HistorianDataSourceHealthSnapshotTests
{
///
/// Drives the "half-published" state directly via reflection: set _connection
/// to a non-null sentinel but leave _activeProcessNode null. The snapshot must
/// report ProcessConnectionOpen = false and ActiveProcessNode = null
/// consistently — never a mismatch.
///
[Fact]
public void Snapshot_with_connection_set_but_active_node_null_is_consistent()
{
var ds = new HistorianDataSource(
new HistorianConfiguration { Enabled = true, ServerName = "h1" });
SetField(ds, "_connection", new HistorianAccess());
SetField(ds, "_activeProcessNode", (string?)null);
var snap = ds.GetHealthSnapshot();
(snap.ProcessConnectionOpen == (snap.ActiveProcessNode != null)).ShouldBeTrue(
"snapshot must not advertise open with no node — picks one source of truth");
}
///
/// Symmetric case for the event connection.
///
[Fact]
public void Snapshot_with_event_connection_set_but_active_node_null_is_consistent()
{
var ds = new HistorianDataSource(
new HistorianConfiguration { Enabled = true, ServerName = "h1" });
SetField(ds, "_eventConnection", new HistorianAccess());
SetField(ds, "_activeEventNode", (string?)null);
var snap = ds.GetHealthSnapshot();
(snap.EventConnectionOpen == (snap.ActiveEventNode != null)).ShouldBeTrue(
"snapshot must not advertise event open with no node");
}
///
/// The other direction: connection cleared but node still populated (the failure path
/// between the two field clears). The snapshot must still pair them consistently.
///
[Fact]
public void Snapshot_with_connection_cleared_but_active_node_populated_is_consistent()
{
var ds = new HistorianDataSource(
new HistorianConfiguration { Enabled = true, ServerName = "h1" });
SetField(ds, "_connection", (HistorianAccess?)null);
SetField(ds, "_activeProcessNode", "node-stale");
var snap = ds.GetHealthSnapshot();
(snap.ProcessConnectionOpen == (snap.ActiveProcessNode != null)).ShouldBeTrue(
"snapshot must not advertise closed with a node still set");
}
///
/// Steady-state happy path: both fields populated — snapshot reports both consistently.
///
[Fact]
public void Snapshot_with_both_fields_populated_reports_open_and_active_node()
{
var ds = new HistorianDataSource(
new HistorianConfiguration { Enabled = true, ServerName = "h1" });
SetField(ds, "_connection", new HistorianAccess());
SetField(ds, "_activeProcessNode", "h1");
var snap = ds.GetHealthSnapshot();
snap.ProcessConnectionOpen.ShouldBeTrue();
snap.ActiveProcessNode.ShouldBe("h1");
}
///
/// Steady-state default (no connect attempted): both null.
///
[Fact]
public void Snapshot_with_default_fields_reports_closed_with_no_active_node()
{
var ds = new HistorianDataSource(
new HistorianConfiguration { Enabled = true, ServerName = "h1" });
var snap = ds.GetHealthSnapshot();
snap.ProcessConnectionOpen.ShouldBeFalse();
snap.ActiveProcessNode.ShouldBeNull();
snap.EventConnectionOpen.ShouldBeFalse();
snap.ActiveEventNode.ShouldBeNull();
}
private static void SetField(object target, string name, object? value)
{
var f = target.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic);
f.ShouldNotBeNull($"private field '{name}' must exist on {target.GetType().Name}");
f!.SetValue(target, value);
}
}