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
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:
+25
@@ -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)
|
||||
{
|
||||
|
||||
+7
@@ -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
@@ -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)
|
||||
{
|
||||
|
||||
+5
@@ -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()
|
||||
{
|
||||
|
||||
+4
@@ -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
|
||||
|
||||
+12
@@ -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()
|
||||
{
|
||||
|
||||
+8
@@ -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
|
||||
|
||||
+19
@@ -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}");
|
||||
|
||||
Reference in New Issue
Block a user