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
@@ -7,6 +7,7 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
public sealed class DeferredAddressSpaceSinkTests
{
/// <summary>Verifies that the default inner is a null sink so calls before SetSink are safe.</summary>
[Fact]
public void Default_inner_is_null_sink_so_calls_before_SetSink_are_safe()
{
@@ -18,6 +19,7 @@ public sealed class DeferredAddressSpaceSinkTests
deferred.RebuildAddressSpace();
}
/// <summary>Verifies that calls after SetSink are forwarded to the inner sink.</summary>
[Fact]
public void Calls_after_SetSink_are_forwarded_to_the_inner()
{
@@ -32,6 +34,7 @@ public sealed class DeferredAddressSpaceSinkTests
inner.Calls.ShouldBe(new[] { "WV:x", "WA:a-1", "RB" });
}
/// <summary>Verifies that setting sink to null reverts to null sink.</summary>
[Fact]
public void SetSink_to_null_reverts_to_null_sink()
{
@@ -46,6 +49,7 @@ public sealed class DeferredAddressSpaceSinkTests
inner.Calls.Count.ShouldBe(1);
}
/// <summary>Verifies that sink can be swapped between implementations.</summary>
[Fact]
public void SetSink_can_swap_between_implementations()
{
@@ -65,15 +69,21 @@ public sealed class DeferredAddressSpaceSinkTests
private sealed class RecordingSink : IOpcUaAddressSpaceSink
{
/// <summary>Gets the queue of recorded calls.</summary>
public ConcurrentQueue<string> CallQueue { get; } = new();
/// <summary>Gets the list of recorded calls.</summary>
public List<string> Calls => CallQueue.ToList();
/// <inheritdoc />
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc)
=> CallQueue.Enqueue($"WV:{nodeId}");
/// <inheritdoc />
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime sourceTimestampUtc)
=> CallQueue.Enqueue($"WA:{alarmNodeId}");
/// <inheritdoc />
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName)
=> CallQueue.Enqueue($"EF:{folderNodeId}");
/// <inheritdoc />
public void RebuildAddressSpace() => CallQueue.Enqueue("RB");
}
}
@@ -6,6 +6,7 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
public sealed class DeferredServiceLevelPublisherTests
{
/// <summary>Verifies that publish before SetInner is a safe no-op.</summary>
[Fact]
public void Publish_before_SetInner_is_a_safe_noop()
{
@@ -14,6 +15,7 @@ public sealed class DeferredServiceLevelPublisherTests
Should.NotThrow(() => deferred.Publish(123));
}
/// <summary>Verifies that publish after SetInner routes to the inner publisher.</summary>
[Fact]
public void Publish_after_SetInner_routes_to_the_inner()
{
@@ -26,6 +28,7 @@ public sealed class DeferredServiceLevelPublisherTests
recording.LastValue.ShouldBe((byte)200);
}
/// <summary>Verifies that SetInner with null reverts to the null publisher.</summary>
[Fact]
public void SetInner_null_reverts_to_Null_publisher()
{
@@ -42,7 +45,10 @@ public sealed class DeferredServiceLevelPublisherTests
private sealed class RecordingPublisher : IServiceLevelPublisher
{
/// <summary>Gets the last published service level value.</summary>
public byte? LastValue { get; private set; }
/// <summary>Publishes a service level value.</summary>
/// <param name="serviceLevel">The service level to publish.</param>
public void Publish(byte serviceLevel) => LastValue = serviceLevel;
}
}
@@ -17,6 +17,7 @@ public sealed class OpcUaApplicationHostImpersonationTests
private static readonly UserTokenPolicy UserNamePolicy = new(UserTokenType.UserName) { PolicyId = "username_basic256sha256" };
private static readonly UserTokenPolicy AnonPolicy = new(UserTokenType.Anonymous) { PolicyId = "anonymous" };
/// <summary>Verifies successful UserName token impersonation sets identity and clears validation error.</summary>
[Fact]
public void HandleImpersonation_username_success_sets_identity_and_no_validation_error()
{
@@ -33,6 +34,7 @@ public sealed class OpcUaApplicationHostImpersonationTests
authenticator.LastPassword.ShouldBe("secret");
}
/// <summary>Verifies failed UserName token impersonation sets validation error and clears identity.</summary>
[Fact]
public void HandleImpersonation_username_denial_sets_validation_error_and_no_identity()
{
@@ -47,6 +49,7 @@ public sealed class OpcUaApplicationHostImpersonationTests
args.IdentityValidationError.LocalizedText.Text.ShouldContain("Invalid credentials");
}
/// <summary>Verifies anonymous identity tokens are passed through to the SDK default handler.</summary>
[Fact]
public void HandleImpersonation_anonymous_token_falls_through_to_sdk_default()
{
@@ -61,6 +64,7 @@ public sealed class OpcUaApplicationHostImpersonationTests
authenticator.LastUsername.ShouldBeNull("anonymous tokens must not hit the authenticator");
}
/// <summary>Verifies authenticator exceptions result in rejection with validation error.</summary>
[Fact]
public void HandleImpersonation_authenticator_throwing_results_in_rejection()
{
@@ -74,6 +78,7 @@ public sealed class OpcUaApplicationHostImpersonationTests
args.IdentityValidationError.Code.ShouldBe(StatusCodes.BadIdentityTokenRejected);
}
/// <summary>Verifies null username is normalized to empty string before authenticator call.</summary>
[Fact]
public void HandleImpersonation_null_username_treated_as_empty_string()
{
@@ -86,6 +91,7 @@ public sealed class OpcUaApplicationHostImpersonationTests
authenticator.LastUsername.ShouldBe(string.Empty);
}
/// <summary>Verifies NullOpcUaUserAuthenticator always returns denial result.</summary>
[Fact]
public async Task NullOpcUaUserAuthenticator_always_denies()
{
@@ -99,9 +105,16 @@ public sealed class OpcUaApplicationHostImpersonationTests
private sealed class RecordingAuthenticator(OpcUaUserAuthResult outcome) : IOpcUaUserAuthenticator
{
/// <summary>Gets the username passed to the last authentication call.</summary>
public string? LastUsername { get; private set; }
/// <summary>Gets the password passed to the last authentication call.</summary>
public string? LastPassword { get; private set; }
/// <summary>Authenticates a username and password, recording them for inspection by tests.</summary>
/// <param name="username">The username to authenticate.</param>
/// <param name="password">The password to authenticate.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>The predefined authentication result.</returns>
public Task<OpcUaUserAuthResult> AuthenticateUserNameAsync(string username, string password, CancellationToken ct)
{
LastUsername = username;
@@ -110,8 +123,14 @@ public sealed class OpcUaApplicationHostImpersonationTests
}
}
/// <summary>Test authenticator that throws an exception during authentication.</summary>
private sealed class ThrowingAuthenticator(Exception ex) : IOpcUaUserAuthenticator
{
/// <summary>Authenticates by throwing the configured exception.</summary>
/// <param name="username">The username to authenticate.</param>
/// <param name="password">The password to authenticate.</param>
/// <param name="ct">The cancellation token.</param>
/// <returns>Never returns; always throws the configured exception.</returns>
public Task<OpcUaUserAuthResult> AuthenticateUserNameAsync(string username, string password, CancellationToken ct)
=> Task.FromException<OpcUaUserAuthResult>(ex);
}
@@ -20,6 +20,9 @@ public sealed class OpcUaApplicationHostSecurityTests : IDisposable
Path.GetTempPath(),
$"otopcua-pki-{Guid.NewGuid():N}");
/// <summary>
/// Verifies that BuildSecurityPolicies emits all three baseline security profiles.
/// </summary>
[Fact]
public void BuildSecurityPolicies_default_set_emits_all_three_baseline_profiles()
{
@@ -39,6 +42,9 @@ public sealed class OpcUaApplicationHostSecurityTests : IDisposable
policies[2].SecurityPolicyUri.ShouldBe(SecurityPolicies.Basic256Sha256);
}
/// <summary>
/// Verifies that BuildSecurityPolicies deduplicates repeated profiles.
/// </summary>
[Fact]
public void BuildSecurityPolicies_dedupes_repeated_profiles()
{
@@ -54,6 +60,9 @@ public sealed class OpcUaApplicationHostSecurityTests : IDisposable
policies[1].SecurityMode.ShouldBe(MessageSecurityMode.None);
}
/// <summary>
/// Verifies that BuildSecurityPolicies falls back to None when given empty input.
/// </summary>
[Fact]
public void BuildSecurityPolicies_empty_input_falls_back_to_none()
{
@@ -64,6 +73,9 @@ public sealed class OpcUaApplicationHostSecurityTests : IDisposable
policies[0].SecurityPolicyUri.ShouldBe(SecurityPolicies.None);
}
/// <summary>
/// Verifies that BuildUserTokenPolicies emits anonymous and username policies.
/// </summary>
[Fact]
public void BuildUserTokenPolicies_emits_anonymous_and_username()
{
@@ -76,6 +88,9 @@ public sealed class OpcUaApplicationHostSecurityTests : IDisposable
userName.SecurityPolicyUri.ShouldBe(SecurityPolicies.Basic256Sha256);
}
/// <summary>
/// Verifies that StartAsync populates ServerConfiguration with all enabled security profiles.
/// </summary>
[Fact]
public async Task StartAsync_populates_ServerConfiguration_with_all_enabled_profiles()
{
@@ -111,6 +126,9 @@ public sealed class OpcUaApplicationHostSecurityTests : IDisposable
modes.ShouldBe(new[] { MessageSecurityMode.None, MessageSecurityMode.Sign, MessageSecurityMode.SignAndEncrypt });
}
/// <summary>
/// Verifies that StartAsync with only SignAndEncrypt omits the None endpoint.
/// </summary>
[Fact]
public async Task StartAsync_with_only_signandencrypt_omits_None_endpoint()
{
@@ -146,6 +164,9 @@ public sealed class OpcUaApplicationHostSecurityTests : IDisposable
return port;
}
/// <summary>
/// Cleans up temporary PKI files.
/// </summary>
public void Dispose()
{
if (Directory.Exists(_pkiRoot))
@@ -17,6 +17,9 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
/// </summary>
public sealed class OpcUaApplicationHostServerArrayTests
{
/// <summary>
/// Verifies that ServerArray contains local URI and configured peer URIs after start.
/// </summary>
[Fact]
public async Task ServerArray_contains_local_uri_and_configured_peers_after_start()
{
@@ -18,6 +18,7 @@ public sealed class OpcUaApplicationHostTests : IDisposable
Path.GetTempPath(),
$"otopcua-pki-{Guid.NewGuid():N}");
/// <summary>Verifies StartAsync creates a self-signed certificate in the PKI own store.</summary>
[Fact]
public async Task StartAsync_creates_application_certificate_in_pki_own()
{
@@ -39,6 +40,7 @@ public sealed class OpcUaApplicationHostTests : IDisposable
Directory.EnumerateFiles(ownCerts).ShouldNotBeEmpty("expected a self-signed cert file in the own store");
}
/// <summary>Verifies StartAsync reuses an existing certificate on the second boot.</summary>
[Fact]
public async Task StartAsync_reuses_existing_certificate_on_second_boot()
{
@@ -89,6 +91,7 @@ public sealed class OpcUaApplicationHostTests : IDisposable
return port;
}
/// <summary>Cleans up the temporary PKI directory.</summary>
public void Dispose()
{
if (Directory.Exists(_pkiRoot))
@@ -22,6 +22,7 @@ public sealed class Phase7ApplierHierarchyTests : IDisposable
Path.GetTempPath(),
$"otopcua-pki-{Guid.NewGuid():N}");
/// <summary>Verifies that MaterialiseHierarchy creates areas, lines, and equipment with correct parent relationships.</summary>
[Fact]
public void MaterialiseHierarchy_creates_areas_then_lines_then_equipment_with_correct_parents()
{
@@ -44,6 +45,7 @@ public sealed class Phase7ApplierHierarchyTests : IDisposable
calls[2].ShouldBe(("eq-1", "line-1", "Pump-1"));
}
/// <summary>Verifies that orphan equipment without a parent line appears under root.</summary>
[Fact]
public void MaterialiseHierarchy_orphan_equipment_hangs_under_root()
{
@@ -62,6 +64,7 @@ public sealed class Phase7ApplierHierarchyTests : IDisposable
sink.Calls.Single().ShouldBe(("eq-orphan", null, "Orphan"));
}
/// <summary>Verifies that MaterialiseHierarchy creates folder nodes in a real SDK node manager.</summary>
[Fact]
public async Task MaterialiseHierarchy_against_real_SDK_node_manager_creates_folder_nodes()
{
@@ -112,6 +115,7 @@ public sealed class Phase7ApplierHierarchyTests : IDisposable
return port;
}
/// <summary>Disposes of resources allocated by this test class.</summary>
public void Dispose()
{
if (Directory.Exists(_pkiRoot))
@@ -124,12 +128,28 @@ public sealed class Phase7ApplierHierarchyTests : IDisposable
private sealed class RecordingFolderSink : IOpcUaAddressSpaceSink
{
private readonly ConcurrentQueue<(string NodeId, string? Parent, string DisplayName)> _calls = new();
/// <summary>Gets the list of EnsureFolder calls recorded by this sink.</summary>
public List<(string NodeId, string? Parent, string DisplayName)> Calls => _calls.ToList();
/// <summary>Records a value write (stub implementation for testing).</summary>
/// <param name="nodeId">The node ID of the variable.</param>
/// <param name="value">The value to write.</param>
/// <param name="quality">The OPC UA quality value.</param>
/// <param name="sourceTimestampUtc">The source timestamp in UTC.</param>
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc) { }
/// <summary>Records an alarm state write (stub implementation for testing).</summary>
/// <param name="alarmNodeId">The node ID of the alarm condition.</param>
/// <param name="active">Whether the alarm is active.</param>
/// <param name="acknowledged">Whether the alarm has been acknowledged.</param>
/// <param name="sourceTimestampUtc">The source timestamp in UTC.</param>
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime sourceTimestampUtc) { }
/// <summary>Records a folder creation request.</summary>
/// <param name="folderNodeId">The node ID of the folder.</param>
/// <param name="parentNodeId">The node ID of the parent folder, or null for root.</param>
/// <param name="displayName">The display name of the folder.</param>
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName)
=> _calls.Enqueue((folderNodeId, parentNodeId, displayName));
/// <summary>Rebuilds the address space (stub implementation for testing).</summary>
public void RebuildAddressSpace() { }
}
}
@@ -8,6 +8,7 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
public sealed class Phase7ApplierTests
{
/// <summary>Verifies that an empty plan does not call the sink or trigger a rebuild.</summary>
[Fact]
public void Empty_plan_does_not_call_sink_and_does_not_trigger_rebuild()
{
@@ -24,6 +25,7 @@ public sealed class Phase7ApplierTests
sink.AlarmWrites.ShouldBeEmpty();
}
/// <summary>Verifies that removed equipment writes inactive alarm state and triggers rebuild.</summary>
[Fact]
public void Removed_equipment_writes_inactive_alarm_state_per_id_and_triggers_rebuild()
{
@@ -40,6 +42,7 @@ public sealed class Phase7ApplierTests
sink.RebuildCalls.ShouldBe(1);
}
/// <summary>Verifies that added equipment triggers rebuild without writing alarm state.</summary>
[Fact]
public void Added_equipment_triggers_rebuild_without_alarm_writes()
{
@@ -65,6 +68,7 @@ public sealed class Phase7ApplierTests
sink.RebuildCalls.ShouldBe(1);
}
/// <summary>Verifies that driver-only changes do not trigger address space rebuild.</summary>
[Fact]
public void Driver_only_changes_do_not_trigger_address_space_rebuild()
{
@@ -93,6 +97,7 @@ public sealed class Phase7ApplierTests
sink.RebuildCalls.ShouldBe(0);
}
/// <summary>Verifies that sink exceptions in WriteAlarmState do not propagate and rebuild still fires.</summary>
[Fact]
public void Sink_exception_in_WriteAlarmState_does_not_propagate_and_rebuild_still_fires()
{
@@ -124,32 +129,70 @@ public sealed class Phase7ApplierTests
private sealed class RecordingSink : IOpcUaAddressSpaceSink
{
/// <summary>Gets the queue of alarm state write calls.</summary>
public ConcurrentQueue<(string NodeId, bool Active, bool Acknowledged)> AlarmQueue { get; } = new();
/// <summary>Gets the queue of folder creation calls.</summary>
public ConcurrentQueue<(string NodeId, string? Parent, string DisplayName)> FolderQueue { get; } = new();
/// <summary>Gets the number of rebuild calls made on this sink.</summary>
public int RebuildCalls;
/// <summary>Gets the list of recorded alarm writes.</summary>
public List<(string NodeId, bool Active, bool Acknowledged)> AlarmWrites => AlarmQueue.ToList();
/// <summary>Gets the list of recorded folder creation calls.</summary>
public List<(string NodeId, string? Parent, string DisplayName)> FolderCalls => FolderQueue.ToList();
/// <summary>Records a value write (no-op in this recording sink).</summary>
/// <param name="nodeId">The node ID.</param>
/// <param name="value">The value to write.</param>
/// <param name="quality">The OPC UA quality.</param>
/// <param name="sourceTimestampUtc">The source timestamp in UTC.</param>
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc) { }
/// <summary>Records an alarm state write call.</summary>
/// <param name="alarmNodeId">The alarm node ID.</param>
/// <param name="active">Whether the alarm is active.</param>
/// <param name="acknowledged">Whether the alarm is acknowledged.</param>
/// <param name="sourceTimestampUtc">The source timestamp in UTC.</param>
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime sourceTimestampUtc)
=> AlarmQueue.Enqueue((alarmNodeId, active, acknowledged));
/// <summary>Records a folder creation call.</summary>
/// <param name="folderNodeId">The folder node ID.</param>
/// <param name="parentNodeId">The parent folder node ID, if any.</param>
/// <param name="displayName">The display name for the folder.</param>
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName)
=> FolderQueue.Enqueue((folderNodeId, parentNodeId, displayName));
/// <summary>Records a rebuild address space call.</summary>
public void RebuildAddressSpace() => Interlocked.Increment(ref RebuildCalls);
}
private sealed class ThrowingSink : IOpcUaAddressSpaceSink
{
private readonly bool _throwOnAlarmWrite;
/// <summary>Initializes a new instance of the ThrowingSink class.</summary>
/// <param name="throwOnAlarmWrite">Whether to throw on alarm state writes.</param>
public ThrowingSink(bool throwOnAlarmWrite) { _throwOnAlarmWrite = throwOnAlarmWrite; }
/// <summary>Records a value write (no-op in this sink).</summary>
/// <param name="nodeId">The node ID.</param>
/// <param name="value">The value to write.</param>
/// <param name="quality">The OPC UA quality.</param>
/// <param name="sourceTimestampUtc">The source timestamp in UTC.</param>
public void WriteValue(string nodeId, object? value, OpcUaQuality quality, DateTime sourceTimestampUtc) { }
/// <summary>Throws an exception if configured to do so.</summary>
/// <param name="alarmNodeId">The alarm node ID.</param>
/// <param name="active">Whether the alarm is active.</param>
/// <param name="acknowledged">Whether the alarm is acknowledged.</param>
/// <param name="sourceTimestampUtc">The source timestamp in UTC.</param>
/// <exception cref="InvalidOperationException">Thrown when configured to throw on alarm write.</exception>
public void WriteAlarmState(string alarmNodeId, bool active, bool acknowledged, DateTime sourceTimestampUtc)
{
if (_throwOnAlarmWrite) throw new InvalidOperationException("simulated sink fault");
}
/// <summary>No-op folder creation call.</summary>
/// <param name="folderNodeId">The folder node ID.</param>
/// <param name="parentNodeId">The parent folder node ID, if any.</param>
/// <param name="displayName">The display name for the folder.</param>
public void EnsureFolder(string folderNodeId, string? parentNodeId, string displayName) { }
/// <summary>No-op rebuild address space call.</summary>
public void RebuildAddressSpace() { }
}
}
@@ -6,6 +6,7 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
public sealed class Phase7ComposerPurityTests
{
/// <summary>Verifies empty inputs produce empty result.</summary>
[Fact]
public void Empty_inputs_produce_empty_result()
{
@@ -19,6 +20,7 @@ public sealed class Phase7ComposerPurityTests
result.ScriptedAlarmPlans.ShouldBeEmpty();
}
/// <summary>Verifies same inputs in different order produce structurally equal results.</summary>
[Fact]
public void Same_inputs_in_different_order_produce_structurally_equal_results()
{
@@ -44,6 +46,7 @@ public sealed class Phase7ComposerPurityTests
r1.ScriptedAlarmPlans.ShouldBe(r2.ScriptedAlarmPlans);
}
/// <summary>Verifies Compose is pure with identical repeated calls.</summary>
[Fact]
public void Compose_is_pure_repeated_call_returns_element_identical_output()
{
@@ -61,6 +64,7 @@ public sealed class Phase7ComposerPurityTests
r1.ScriptedAlarmPlans.ShouldBe(r2.ScriptedAlarmPlans);
}
/// <summary>Verifies output is sorted by natural key.</summary>
[Fact]
public void Output_is_sorted_by_natural_key()
{
@@ -5,6 +5,7 @@ namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests;
public sealed class Phase7PlannerTests
{
/// <summary>Verifies that empty inputs produce an empty plan.</summary>
[Fact]
public void Empty_inputs_produce_empty_plan()
{
@@ -16,6 +17,7 @@ public sealed class Phase7PlannerTests
plan.IsEmpty.ShouldBeTrue();
}
/// <summary>Verifies that identical compositions produce an empty plan.</summary>
[Fact]
public void Identical_compositions_produce_empty_plan()
{
@@ -28,6 +30,7 @@ public sealed class Phase7PlannerTests
plan.IsEmpty.ShouldBeTrue();
}
/// <summary>Verifies that new equipment goes to the AddedEquipment list.</summary>
[Fact]
public void New_equipment_goes_to_AddedEquipment()
{
@@ -44,6 +47,7 @@ public sealed class Phase7PlannerTests
plan.ChangedEquipment.ShouldBeEmpty();
}
/// <summary>Verifies that disappeared equipment goes to the RemovedEquipment list.</summary>
[Fact]
public void Disappeared_equipment_goes_to_RemovedEquipment()
{
@@ -59,6 +63,7 @@ public sealed class Phase7PlannerTests
plan.AddedEquipment.ShouldBeEmpty();
}
/// <summary>Verifies that equipment with same id but different display name routes to ChangedEquipment.</summary>
[Fact]
public void Same_id_with_different_display_name_routes_to_ChangedEquipment()
{
@@ -79,6 +84,7 @@ public sealed class Phase7PlannerTests
plan.RemovedEquipment.ShouldBeEmpty();
}
/// <summary>Verifies that driver config changes route to ChangedDrivers.</summary>
[Fact]
public void Driver_config_change_routes_to_ChangedDrivers()
{
@@ -96,6 +102,7 @@ public sealed class Phase7PlannerTests
plan.ChangedDrivers.Single().Current.ConfigJson.ShouldContain("new");
}
/// <summary>Verifies that alarm message template changes route to ChangedAlarms.</summary>
[Fact]
public void Alarm_message_template_change_routes_to_ChangedAlarms()
{
@@ -113,6 +120,7 @@ public sealed class Phase7PlannerTests
plan.ChangedAlarms.Single().Current.MessageTemplate.ShouldBe("new");
}
/// <summary>Verifies that added and removed lists are sorted by id for deterministic ordering.</summary>
[Fact]
public void Added_and_removed_lists_are_sorted_by_id_for_deterministic_ordering()
{
@@ -127,6 +135,7 @@ public sealed class Phase7PlannerTests
plan.RemovedEquipment.Select(e => e.EquipmentId).ShouldBe(new[] { "a", "z" });
}
/// <summary>Verifies that mixed changes across all three classes are captured in one pass.</summary>
[Fact]
public void Mixed_changes_across_all_three_classes_are_captured_in_one_pass()
{
@@ -19,6 +19,7 @@ public sealed class SdkAddressSpaceSinkTests : IDisposable
Path.GetTempPath(),
$"otopcua-sink-{Guid.NewGuid():N}");
/// <summary>Verifies that WriteValue creates and updates variables in the OPC UA node manager.</summary>
[Fact]
public async Task WriteValue_creates_and_updates_variable_in_node_manager()
{
@@ -34,6 +35,7 @@ public sealed class SdkAddressSpaceSinkTests : IDisposable
await host.DisposeAsync();
}
/// <summary>Verifies that WriteAlarmState creates a dedicated node distinct from value writes.</summary>
[Fact]
public async Task WriteAlarmState_creates_dedicated_node_distinct_from_value_writes()
{
@@ -48,6 +50,7 @@ public sealed class SdkAddressSpaceSinkTests : IDisposable
await host.DisposeAsync();
}
/// <summary>Verifies that RebuildAddressSpace clears all registered variables.</summary>
[Fact]
public async Task RebuildAddressSpace_clears_all_registered_variables()
{
@@ -69,6 +72,7 @@ public sealed class SdkAddressSpaceSinkTests : IDisposable
await host.DisposeAsync();
}
/// <summary>Verifies that NullOpcUaAddressSpaceSink does not crash on any call.</summary>
[Fact]
public async Task NullOpcUaAddressSpaceSink_does_not_crash_on_any_call()
{
@@ -108,6 +112,7 @@ public sealed class SdkAddressSpaceSinkTests : IDisposable
return port;
}
/// <summary>Cleans up the PKI root directory.</summary>
public void Dispose()
{
if (Directory.Exists(_pkiRoot))
@@ -19,6 +19,7 @@ public sealed class SdkServiceLevelPublisherTests : IDisposable
Path.GetTempPath(),
$"otopcua-pki-{Guid.NewGuid():N}");
/// <summary>Verifies that the publisher writes values to the standard Server.ServiceLevel variable.</summary>
[Fact]
public async Task Publish_writes_value_to_Server_ServiceLevel_variable()
{
@@ -47,6 +48,7 @@ public sealed class SdkServiceLevelPublisherTests : IDisposable
variable.Value.ShouldBe((byte)200);
}
/// <summary>Verifies that publishing service level values is idempotent when called multiple times.</summary>
[Fact]
public async Task Publish_is_idempotent_when_called_multiple_times()
{
@@ -83,6 +85,7 @@ public sealed class SdkServiceLevelPublisherTests : IDisposable
return port;
}
/// <summary>Disposes and cleans up the test resources.</summary>
public void Dispose()
{
if (Directory.Exists(_pkiRoot))