docs: backfill XML documentation across 756 files
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped

Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public
members surfaced by commentchecker — resolves 5,847 of 5,869 issues
(99.6%) across three /fixdocs passes.
This commit is contained in:
Joseph Doherty
2026-05-28 08:10:17 -04:00
parent f9fc7dd2e1
commit 64e3fbe035
756 changed files with 9876 additions and 96 deletions
@@ -21,6 +21,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
[Trait("Category", "Unit")]
public sealed class AahClientManagedAlarmEventWriterTests
{
/// <summary>Verifies that an empty batch returns an empty array without invoking the backend.</summary>
[Fact]
public async Task Empty_batch_returns_empty_array_without_invoking_backend()
{
@@ -33,6 +34,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
backend.Calls.ShouldBe(0);
}
/// <summary>Verifies that a single acknowledgment outcome maps to true.</summary>
[Fact]
public async Task Single_ack_outcome_maps_to_true()
{
@@ -44,6 +46,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
result.ShouldBe(new[] { true });
}
/// <summary>Verifies that a mixed batch preserves per-slot outcome ordering.</summary>
[Fact]
public async Task Mixed_batch_preserves_per_slot_ordering()
{
@@ -65,6 +68,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
result.ShouldBe(new[] { true, false, false, true });
}
/// <summary>Verifies that backend exceptions mark the whole batch as RetryPlease.</summary>
[Fact]
public async Task Backend_exception_marks_whole_batch_RetryPlease()
{
@@ -80,6 +84,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
result.ShouldBe(new[] { false, false, false });
}
/// <summary>Verifies that cancellation propagates from the backend.</summary>
[Fact]
public async Task Cancellation_propagates_from_backend()
{
@@ -91,6 +96,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
ex.ShouldNotBeNull();
}
/// <summary>Verifies that a backend returning the wrong outcome count degrades to RetryPlease.</summary>
[Fact]
public async Task Backend_returning_wrong_count_degrades_to_RetryPlease()
{
@@ -108,6 +114,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
result.ShouldBe(new[] { false });
}
/// <summary>Verifies that a large batch with all acknowledgments returns all true outcomes.</summary>
/// <param name="batchSize">The batch size to test.</param>
[Theory]
[InlineData(100)]
[InlineData(1000)]
@@ -129,6 +137,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
backend.Calls.ShouldBe(1);
}
/// <summary>Verifies that a large batch with alternating outcomes preserves positional ordering.</summary>
/// <param name="batchSize">The batch size to test.</param>
[Theory]
[InlineData(100)]
[InlineData(1000)]
@@ -154,6 +164,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
}
}
/// <summary>Verifies that retry then succeed correctly simulates cluster failover.</summary>
[Fact]
public async Task Backend_retry_then_succeed_simulates_cluster_failover()
{
@@ -194,6 +205,11 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
backend.Calls.ShouldBe(2);
}
/// <summary>Verifies outcome mapping across various HRESULT and error condition combinations.</summary>
/// <param name="hresult">The HRESULT code to test.</param>
/// <param name="isCommunicationError">Whether the error is a communication error.</param>
/// <param name="isMalformedInput">Whether the input is malformed.</param>
/// <param name="expected">The expected outcome.</param>
[Theory]
// hresult 0 + clean → Ack
[InlineData(0, false, false, AlarmHistorianWriteOutcome.Ack)]
@@ -224,16 +240,25 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
AckComment = null,
};
/// <summary>Test double that records calls and returns outcomes via a delegate.</summary>
private sealed class RecordingBackend : IAlarmHistorianWriteBackend
{
private readonly Func<AlarmHistorianEventDto[], AlarmHistorianWriteOutcome[]> _produce;
/// <summary>Gets the number of calls recorded.</summary>
public int Calls { get; private set; }
/// <summary>Initializes a new instance of the <see cref="RecordingBackend"/> class.</summary>
/// <param name="produce">A delegate that produces outcomes for the given events.</param>
public RecordingBackend(Func<AlarmHistorianEventDto[], AlarmHistorianWriteOutcome[]> produce)
{
_produce = produce;
}
/// <summary>Records a call and returns outcomes from the delegate.</summary>
/// <param name="events">The events to write.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>The outcomes produced by the delegate.</returns>
public Task<AlarmHistorianWriteOutcome[]> WriteBatchAsync(
AlarmHistorianEventDto[] events, CancellationToken cancellationToken)
{
@@ -16,6 +16,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
FailureCooldownSeconds = 60,
};
/// <summary>Verifies that a single-node configuration falls back to ServerName when ServerNames is empty.</summary>
[Fact]
public void Single_node_config_falls_back_to_ServerName_when_ServerNames_empty()
{
@@ -25,6 +26,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
p.GetHealthyNodes().ShouldBe(new[] { "only-node" });
}
/// <summary>Verifies that a failed node enters cooldown and is skipped from the healthy nodes list.</summary>
[Fact]
public void Failed_node_enters_cooldown_and_is_skipped()
{
@@ -35,6 +37,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
p.GetHealthyNodes().ShouldBe(new[] { "b" });
}
/// <summary>Verifies that the cooldown period expires after the configured time window.</summary>
[Fact]
public void Cooldown_expires_after_configured_window()
{
@@ -47,6 +50,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
p.GetHealthyNodes().ShouldBe(new[] { "a", "b" });
}
/// <summary>Verifies that marking a node healthy immediately clears its cooldown.</summary>
[Fact]
public void MarkHealthy_immediately_clears_cooldown()
{
@@ -58,6 +62,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
p.GetHealthyNodes().ShouldBe(new[] { "a" });
}
/// <summary>Verifies that when all nodes are in cooldown, an empty healthy list is returned.</summary>
[Fact]
public void All_nodes_in_cooldown_returns_empty_healthy_list()
{
@@ -69,6 +74,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
p.NodeCount.ShouldBe(2);
}
/// <summary>Verifies that a snapshot reports failure count and the last error message.</summary>
[Fact]
public void Snapshot_reports_failure_count_and_last_error()
{
@@ -84,6 +90,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
snap.CooldownUntil.ShouldNotBeNull();
}
/// <summary>Verifies that duplicate hostnames are deduplicated case-insensitively.</summary>
[Fact]
public void Duplicate_hostnames_are_deduplicated_case_insensitively()
{
@@ -18,6 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.Backend;
[Trait("Category", "Unit")]
public sealed class HistorianDataSourceConnectFailoverTests
{
/// <summary>Verifies that ReadRaw throws when no nodes are healthy.</summary>
[Fact]
public async Task ReadRaw_when_no_nodes_are_healthy_throws_so_IPC_surfaces_Success_false()
{
@@ -43,6 +44,7 @@ public sealed class HistorianDataSourceConnectFailoverTests
CancellationToken.None));
}
/// <summary>Verifies that ReadRaw tries each cluster node in order.</summary>
[Fact]
public async Task ReadRaw_tries_each_cluster_node_in_order_until_one_succeeds_or_all_fail()
{
@@ -68,6 +70,7 @@ public sealed class HistorianDataSourceConnectFailoverTests
factory.AttemptedNodes.ShouldBe(new[] { "node-a", "node-b", "node-c" });
}
/// <summary>Verifies that failed nodes are marked in cooldown and not retried immediately.</summary>
[Fact]
public async Task ReadRaw_marks_failed_nodes_in_cooldown_so_a_subsequent_call_sees_no_healthy_nodes()
{
@@ -92,6 +95,7 @@ public sealed class HistorianDataSourceConnectFailoverTests
snap.ActiveProcessNode.ShouldBeNull();
}
/// <summary>Verifies that ReadEvents uses a separate event connection path.</summary>
[Fact]
public async Task ReadEvents_uses_a_separate_event_connection_path()
{
@@ -121,6 +125,12 @@ public sealed class HistorianDataSourceConnectFailoverTests
private sealed class ThrowingConnectionFactory : IHistorianConnectionFactory
{
/// <summary>
/// Simulates a connection failure by throwing an exception.
/// </summary>
/// <param name="config">The historian configuration.</param>
/// <param name="type">The connection type.</param>
/// <param name="readOnly">Whether to open a read-only connection.</param>
public HistorianAccess CreateAndConnect(
HistorianConfiguration config, HistorianConnectionType type, bool readOnly = true)
=> throw new InvalidOperationException($"simulated connect failure to {config.ServerName}");
@@ -128,9 +138,17 @@ public sealed class HistorianDataSourceConnectFailoverTests
private sealed class TrackingThrowingConnectionFactory : IHistorianConnectionFactory
{
/// <summary>Gets the list of node names that were attempted.</summary>
public List<string> AttemptedNodes { get; } = new();
/// <summary>Gets the list of connection types that were attempted.</summary>
public List<HistorianConnectionType> AttemptedTypes { get; } = new();
/// <summary>
/// Tracks connection attempts and simulates a connection failure.
/// </summary>
/// <param name="config">The historian configuration.</param>
/// <param name="type">The connection type.</param>
/// <param name="readOnly">Whether to open a read-only connection.</param>
public HistorianAccess CreateAndConnect(
HistorianConfiguration config, HistorianConnectionType type, bool readOnly = true)
{
@@ -24,12 +24,14 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Tests.Backend;
[Trait("Category", "Unit")]
public sealed class HistorianDataSourceRequestTimeoutTests
{
/// <summary>Verifies default request timeout is 60 seconds.</summary>
[Fact]
public void Default_request_timeout_is_60_seconds()
{
new HistorianConfiguration().RequestTimeoutSeconds.ShouldBe(60);
}
/// <summary>Verifies positive request timeout values are applied correctly.</summary>
[Fact]
public void Positive_request_timeout_is_used_verbatim()
{
@@ -44,6 +46,7 @@ public sealed class HistorianDataSourceRequestTimeoutTests
cts.Dispose();
}
/// <summary>Verifies zero or negative timeout values disable the outer safety timeout.</summary>
[Fact]
public void Zero_or_negative_request_timeout_is_treated_as_no_timeout()
{
@@ -61,6 +64,7 @@ public sealed class HistorianDataSourceRequestTimeoutTests
cts.Dispose();
}
/// <summary>Verifies short timeout values correctly fire cancellation on the linked token.</summary>
[Fact]
public async Task Small_timeout_cancels_the_linked_token()
{
@@ -78,6 +82,7 @@ public sealed class HistorianDataSourceRequestTimeoutTests
cts.Dispose();
}
/// <summary>Verifies caller's cancellation token propagates to the linked token.</summary>
[Fact]
public void Inbound_cancellation_propagates_into_the_linked_token()
{
@@ -19,6 +19,8 @@ public sealed class HistorianDataSourceStartQueryClassificationTests
{
// ── Connection-class codes — the connection should be reset ───────────
/// <summary>Verifies that connection-class error codes are classified as connection errors.</summary>
/// <param name="code">The historian error code to test.</param>
[Theory]
[InlineData(HistorianAccessError.ErrorValue.FailedToConnect)]
[InlineData(HistorianAccessError.ErrorValue.FailedToCreateSession)]
@@ -36,6 +38,8 @@ public sealed class HistorianDataSourceStartQueryClassificationTests
// ── Query-class codes — the connection should NOT be reset ────────────
/// <summary>Verifies that query-class error codes are NOT classified as connection errors.</summary>
/// <param name="code">The historian error code to test.</param>
[Theory]
[InlineData(HistorianAccessError.ErrorValue.InvalidArgument)] // bad tag name, etc.
[InlineData(HistorianAccessError.ErrorValue.ValidationFailed)] // bad query args
@@ -21,12 +21,14 @@ public sealed class HistorianDataSourceValueAndAggregateTests
{
// ── SelectValueFromPair ───────────────────────────────────────────────
/// <summary>Verifies that numeric value is returned when StringValue is empty.</summary>
[Fact]
public void SelectValueFromPair_returns_numeric_value_when_StringValue_is_empty()
{
HistorianDataSource.SelectValueFromPair(42.5, string.Empty).ShouldBe(42.5);
}
/// <summary>Verifies that numeric value is returned when Value is non-zero even if StringValue is populated.</summary>
[Fact]
public void SelectValueFromPair_returns_numeric_value_when_Value_is_non_zero_even_with_StringValue_populated()
{
@@ -35,6 +37,7 @@ public sealed class HistorianDataSourceValueAndAggregateTests
HistorianDataSource.SelectValueFromPair(3.14, "3.14").ShouldBe(3.14);
}
/// <summary>Verifies that StringValue is returned when Value is zero and StringValue is non-empty.</summary>
[Fact]
public void SelectValueFromPair_returns_StringValue_when_Value_is_zero_and_StringValue_non_empty()
{
@@ -42,6 +45,7 @@ public sealed class HistorianDataSourceValueAndAggregateTests
HistorianDataSource.SelectValueFromPair(0.0, "Ready").ShouldBe("Ready");
}
/// <summary>Verifies that numeric zero is returned when Value is zero and StringValue is empty.</summary>
[Fact]
public void SelectValueFromPair_returns_numeric_zero_when_Value_is_zero_and_StringValue_empty()
{
@@ -49,12 +53,14 @@ public sealed class HistorianDataSourceValueAndAggregateTests
HistorianDataSource.SelectValueFromPair(0.0, string.Empty).ShouldBe(0.0);
}
/// <summary>Verifies that null StringValue falls back to numeric value.</summary>
[Fact]
public void SelectValueFromPair_null_StringValue_falls_back_to_numeric()
{
HistorianDataSource.SelectValueFromPair(7.7, null).ShouldBe(7.7);
}
/// <summary>Verifies the documented edge case where numeric zero with a formatted string returns the string.</summary>
[Fact]
public void SelectValueFromPair_documented_edge_case_numeric_zero_with_formatted_string_returns_string()
{
@@ -68,6 +74,9 @@ public sealed class HistorianDataSourceValueAndAggregateTests
// ── ExtractAggregateValue ─────────────────────────────────────────────
/// <summary>Verifies that aggregate value extraction dispatches correctly for known columns.</summary>
/// <param name="column">The aggregate result column name to extract.</param>
/// <param name="expected">The expected aggregate double value.</param>
[Theory]
[InlineData("Average", 10.0)]
[InlineData("Minimum", 1.0)]
@@ -89,6 +98,7 @@ public sealed class HistorianDataSourceValueAndAggregateTests
HistorianDataSource.ExtractAggregateValue(result, column).ShouldBe(expected);
}
/// <summary>Verifies that ValueCount is dispatched to the uint field.</summary>
[Fact]
public void ExtractAggregateValue_ValueCount_dispatches_to_uint_field()
{
@@ -97,6 +107,7 @@ public sealed class HistorianDataSourceValueAndAggregateTests
HistorianDataSource.ExtractAggregateValue(result, "ValueCount").ShouldBe(42.0);
}
/// <summary>Verifies that an unknown column returns null.</summary>
[Fact]
public void ExtractAggregateValue_unknown_column_returns_null()
{
@@ -104,6 +115,7 @@ public sealed class HistorianDataSourceValueAndAggregateTests
HistorianDataSource.ExtractAggregateValue(NewAggregateResult(), "NotAColumn").ShouldBeNull();
}
/// <summary>Verifies that aggregate value dispatch is case-sensitive.</summary>
[Fact]
public void ExtractAggregateValue_case_sensitive_dispatch()
{
@@ -14,6 +14,8 @@ public sealed class HistorianQualityMapperTests
/// from sensor issues. After PR 12 every known subcode round-trips to its canonical
/// uint32 StatusCode and Proxy translation stays byte-for-byte with v1 QualityMapper.
/// </summary>
/// <param name="quality">The OPC DA quality code to map.</param>
/// <param name="expected">The expected canonical OPC UA StatusCode.</param>
[Theory]
[InlineData((byte)192, 0x00000000u)] // Good
[InlineData((byte)216, 0x00D80000u)] // Good_LocalOverride
@@ -35,6 +37,8 @@ public sealed class HistorianQualityMapperTests
HistorianQualityMapper.Map(quality).ShouldBe(expected);
}
/// <summary>Verifies that unknown good-family quality codes fall back to plain Good.</summary>
/// <param name="q">The OPC DA quality byte to test.</param>
[Theory]
[InlineData((byte)200)] // Good — unknown subcode in Good family
[InlineData((byte)255)] // Good — unknown
@@ -43,6 +47,8 @@ public sealed class HistorianQualityMapperTests
HistorianQualityMapper.Map(q).ShouldBe(0x00000000u);
}
/// <summary>Verifies that unknown uncertain-family quality codes fall back to plain Uncertain.</summary>
/// <param name="q">The OPC DA quality byte to test.</param>
[Theory]
[InlineData((byte)100)] // Uncertain — unknown subcode
[InlineData((byte)150)] // Uncertain — unknown
@@ -51,6 +57,8 @@ public sealed class HistorianQualityMapperTests
HistorianQualityMapper.Map(q).ShouldBe(0x40000000u);
}
/// <summary>Verifies that unknown bad-family quality codes fall back to plain Bad.</summary>
/// <param name="q">The OPC DA quality byte to test.</param>
[Theory]
[InlineData((byte)1)] // Bad — unknown subcode
[InlineData((byte)50)] // Bad — unknown
@@ -26,6 +26,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
{
// ── Connection-unavailable path (deterministic, no SDK load) ──────────
/// <summary>Verifies that an empty batch returns an empty outcome array.</summary>
[Fact]
public async Task Empty_batch_returns_empty_array()
{
@@ -38,6 +39,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
outcomes.ShouldBeEmpty();
}
/// <summary>Verifies that when all nodes are unreachable, the entire batch is deferred as RetryPlease.</summary>
[Fact]
public async Task Unreachable_node_defers_whole_batch_as_RetryPlease()
{
@@ -54,6 +56,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
outcomes.ShouldAllBe(o => o == AlarmHistorianWriteOutcome.RetryPlease);
}
/// <summary>Verifies that a large batch with unreachable nodes returns one outcome per event.</summary>
[Fact]
public async Task Unreachable_node_large_batch_returns_one_outcome_per_event()
{
@@ -69,6 +72,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
outcomes.ShouldAllBe(o => o == AlarmHistorianWriteOutcome.RetryPlease);
}
/// <summary>Verifies that a connection failure marks the node as failed in the endpoint picker.</summary>
[Fact]
public async Task Connect_failure_marks_node_failed_in_picker()
{
@@ -85,6 +89,9 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
// ── ClassifyOutcome — error-code → outcome mapping ────────────────────
/// <summary>Verifies that error codes map to the expected write outcomes.</summary>
/// <param name="code">The historian access error code to classify.</param>
/// <param name="expected">The expected write outcome.</param>
[Theory]
[InlineData(HistorianAccessError.ErrorValue.Success, AlarmHistorianWriteOutcome.Ack)]
[InlineData(HistorianAccessError.ErrorValue.FailedToConnect, AlarmHistorianWriteOutcome.RetryPlease)]
@@ -105,6 +112,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
// ── ToHistorianEvent — EventId handling ───────────────────────────────
/// <summary>Verifies that a parseable event ID is used verbatim in the historian event.</summary>
[Fact]
public void ToHistorianEvent_parseable_event_id_is_used_verbatim()
{
@@ -123,6 +131,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
#pragma warning restore CS0618
}
/// <summary>Verifies that an unparseable event ID is synthesized as a unique non-empty GUID.</summary>
[Fact]
public void ToHistorianEvent_unparseable_event_id_synthesizes_unique_non_empty_Guid()
{
@@ -155,6 +164,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
idA.ShouldNotBe(idB, "every event needs a unique synthesized id");
}
/// <summary>Verifies that a write-to-read-only-file error is classified as RetryPlease, not PermanentFail.</summary>
[Fact]
public void ClassifyOutcome_WriteToReadOnlyFile_is_RetryPlease_not_PermanentFail()
{
@@ -171,6 +181,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
// ── BuildConnectionArgs — read-only vs write shaping ──────────────────
/// <summary>Verifies that a write connection is opened with ReadOnly set to false.</summary>
[Fact]
public void BuildConnectionArgs_write_connection_is_not_read_only()
{
@@ -184,6 +195,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
args.ServerName.ShouldBe("h1");
}
/// <summary>Verifies that a query connection is opened with ReadOnly set to true.</summary>
[Fact]
public void BuildConnectionArgs_query_connection_is_read_only()
{
@@ -194,6 +206,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
args.ConnectionType.ShouldBe(HistorianConnectionType.Process);
}
/// <summary>Verifies that non-integrated security credentials are preserved in connection arguments.</summary>
[Fact]
public void BuildConnectionArgs_non_integrated_security_carries_credentials()
{
@@ -215,6 +228,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
// The entry point (HistorianAccess.AddStreamedValue) is pinned and implemented;
// these need a live AVEVA Historian and are un-skipped during the PR D.1 smoke.
/// <summary>Verifies that a single alarm event roundtrip returns an Ack outcome.</summary>
[Fact(Skip = "rig-required: needs a live AVEVA Historian — un-skip during the PR D.1 rollout smoke")]
public async Task Live_single_event_roundtrip_returns_Ack()
{
@@ -226,6 +240,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
outcomes[0].ShouldBe(AlarmHistorianWriteOutcome.Ack);
}
/// <summary>Verifies that cluster failover rotates from a bad primary node to a secondary node.</summary>
[Fact(Skip = "rig-required: needs a live AVEVA Historian cluster (two nodes) — un-skip during the PR D.1 rollout smoke")]
public async Task Live_cluster_failover_primary_bad_rotates_to_secondary()
{
@@ -296,6 +311,10 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Host.Tests
/// </summary>
private sealed class ThrowingConnectionFactory : IHistorianConnectionFactory
{
/// <summary>Creates and attempts to connect, always throwing a simulated connect failure.</summary>
/// <param name="config">The historian configuration specifying the target server.</param>
/// <param name="type">The connection type (Process or Event).</param>
/// <param name="readOnly">Whether to open a read-only connection.</param>
public HistorianAccess CreateAndConnect(
HistorianConfiguration config, HistorianConnectionType type, bool readOnly = true)
=> throw new InvalidOperationException($"simulated connect failure to {config.ServerName}");