docs: complete XML doc comments via fixdocs (2757 to 131 findings)

Add missing <returns>/<param>/<summary>/<typeparam> tags and clean up
misused inheritdoc across 481 files so the documented API surface is
complete. Documentation-only (zero code lines changed). The 131 remaining
findings are inheritdoc-style warnings deliberately left to preserve
hand-written implementation rationale (plan-decision notes, race-condition
explanations).
This commit is contained in:
Joseph Doherty
2026-06-03 12:34:34 -04:00
parent c6d9b20d9f
commit bd6c0b4d3d
481 changed files with 2550 additions and 1668 deletions
@@ -10,10 +10,10 @@ public sealed class DriverHostTests
{
private sealed class StubDriver(string id, bool failInit = false) : IDriver
{
/// <summary>Gets the driver instance identifier.</summary>
/// <inheritdoc />
public string DriverInstanceId { get; } = id;
/// <summary>Gets the driver type name.</summary>
/// <inheritdoc />
public string DriverType => "Stub";
/// <summary>Gets a value indicating whether the driver has been initialized.</summary>
@@ -22,9 +22,7 @@ public sealed class DriverHostTests
/// <summary>Gets a value indicating whether the driver has been shut down.</summary>
public bool ShutDown { get; private set; }
/// <summary>Initializes the driver asynchronously.</summary>
/// <param name="_">Configuration data (unused in stub).</param>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task InitializeAsync(string _, CancellationToken ct)
{
if (failInit) throw new InvalidOperationException("boom");
@@ -32,28 +30,25 @@ public sealed class DriverHostTests
return Task.CompletedTask;
}
/// <summary>Reinitializes the driver asynchronously.</summary>
/// <param name="_">Configuration data (unused in stub).</param>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task ReinitializeAsync(string _, CancellationToken ct) => Task.CompletedTask;
/// <summary>Shuts down the driver asynchronously.</summary>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task ShutdownAsync(CancellationToken ct) { ShutDown = true; return Task.CompletedTask; }
/// <summary>Gets the current health status of the driver.</summary>
/// <inheritdoc />
public DriverHealth GetHealth() =>
new(Initialized ? DriverState.Healthy : DriverState.Unknown, null, null);
/// <summary>Gets the memory footprint of the driver.</summary>
/// <inheritdoc />
public long GetMemoryFootprint() => 0;
/// <summary>Flushes optional caches asynchronously.</summary>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task FlushOptionalCachesAsync(CancellationToken ct) => Task.CompletedTask;
}
/// <summary>Verifies that registering a driver initializes it and tracks its health.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Register_initializes_driver_and_tracks_health()
{
@@ -68,6 +63,7 @@ public sealed class DriverHostTests
}
/// <summary>Verifies that registration rethrows initialization failures but keeps the driver registered.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Register_rethrows_init_failure_but_keeps_driver_registered()
{
@@ -81,6 +77,7 @@ public sealed class DriverHostTests
}
/// <summary>Verifies that duplicate driver registration throws an exception.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Duplicate_registration_throws()
{
@@ -92,6 +89,7 @@ public sealed class DriverHostTests
}
/// <summary>Verifies that unregistering a driver shuts it down and removes it.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Unregister_shuts_down_and_removes()
{
@@ -113,6 +111,7 @@ public sealed class DriverHostTests
/// The driver awaits an unsettled TaskCompletionSource so it does not introduce its
/// own capture — only DriverHost's await of the returned Task can drive a post.
/// </summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task RegisterAsync_Does_Not_Capture_SynchronizationContext()
{
@@ -137,6 +136,7 @@ public sealed class DriverHostTests
}
/// <summary>Verifies that UnregisterAsync does not capture the synchronization context.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task UnregisterAsync_Does_Not_Capture_SynchronizationContext()
{
@@ -165,6 +165,7 @@ public sealed class DriverHostTests
}
/// <summary>Verifies that DisposeAsync does not capture the synchronization context.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task DisposeAsync_Does_Not_Capture_SynchronizationContext()
{
@@ -225,34 +226,28 @@ public sealed class DriverHostTests
/// <summary>Driver whose Initialize / Shutdown completions are caller-controlled via TCS.</summary>
private sealed class TcsDriver(string id, TaskCompletionSource initTcs, TaskCompletionSource? shutdownTcs = null) : IDriver
{
/// <summary>Gets the driver instance identifier.</summary>
/// <inheritdoc />
public string DriverInstanceId { get; } = id;
/// <summary>Gets the driver type name.</summary>
/// <inheritdoc />
public string DriverType => "Tcs";
/// <summary>Initializes the driver asynchronously.</summary>
/// <param name="_">Configuration data (unused in TCS driver).</param>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task InitializeAsync(string _, CancellationToken ct) => initTcs.Task;
/// <summary>Reinitializes the driver asynchronously.</summary>
/// <param name="_">Configuration data (unused in TCS driver).</param>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task ReinitializeAsync(string _, CancellationToken ct) => Task.CompletedTask;
/// <summary>Shuts down the driver asynchronously.</summary>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task ShutdownAsync(CancellationToken ct) => (shutdownTcs ?? CompletedTcs).Task;
/// <summary>Gets the current health status of the driver.</summary>
/// <inheritdoc />
public DriverHealth GetHealth() => new(DriverState.Healthy, null, null);
/// <summary>Gets the memory footprint of the driver.</summary>
/// <inheritdoc />
public long GetMemoryFootprint() => 0;
/// <summary>Flushes optional caches asynchronously.</summary>
/// <param name="ct">The cancellation token.</param>
/// <inheritdoc />
public Task FlushOptionalCachesAsync(CancellationToken ct) => Task.CompletedTask;
private static readonly TaskCompletionSource CompletedTcs = MakeCompleted();
@@ -271,7 +266,6 @@ public sealed class DriverHostTests
public int PostCount;
public int SendCount;
/// <summary>Posts a callback to the work queue.</summary>
/// <inheritdoc />
public override void Post(SendOrPostCallback d, object? state)
{
@@ -279,7 +273,6 @@ public sealed class DriverHostTests
_queue.Enqueue(() => d(state));
}
/// <summary>Sends a callback synchronously.</summary>
/// <inheritdoc />
public override void Send(SendOrPostCallback d, object? state)
{
@@ -15,6 +15,7 @@ public sealed class GenericDriverNodeManagerTests
/// This is the plumbing that PR 16's concrete OPC UA builder will use to update the actual
/// AlarmConditionState nodes.
/// </summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Alarm_events_are_routed_to_the_sink_registered_for_the_matching_source_node_id()
{
@@ -45,6 +46,7 @@ public sealed class GenericDriverNodeManagerTests
}
/// <summary>Verifies that non-alarm variables do not register sinks in the alarm tracker.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Non_alarm_variables_do_not_register_sinks()
{
@@ -59,6 +61,7 @@ public sealed class GenericDriverNodeManagerTests
}
/// <summary>Verifies that alarm events with unknown source node IDs are silently dropped.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Unknown_source_node_id_is_dropped_silently()
{
@@ -74,6 +77,7 @@ public sealed class GenericDriverNodeManagerTests
}
/// <summary>Verifies that disposing the node manager unsubscribes from alarm events.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Dispose_unsubscribes_from_OnAlarmEvent()
{
@@ -96,6 +100,7 @@ public sealed class GenericDriverNodeManagerTests
/// must unsubscribe the old alarm forwarder and clear the sink registry before re-walking,
/// so alarm transitions are not delivered twice.
/// </summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Second_BuildAddressSpaceAsync_Does_Not_Double_Fire_Alarms()
{
@@ -121,6 +126,7 @@ public sealed class GenericDriverNodeManagerTests
}
/// <summary>Verifies that a second call to BuildAddressSpaceAsync clears the old sink registry.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Second_BuildAddressSpaceAsync_Clears_Old_Sink_Registry()
{
@@ -137,6 +143,7 @@ public sealed class GenericDriverNodeManagerTests
}
/// <summary>Verifies that calling BuildAddressSpaceAsync after disposal throws ObjectDisposedException.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task BuildAddressSpaceAsync_After_Dispose_Throws_ObjectDisposedException()
{
@@ -154,6 +161,7 @@ public sealed class GenericDriverNodeManagerTests
/// out of <c>BuildAddressSpaceAsync</c> unhandled so the Server layer's per-driver try/catch
/// (<c>OpcUaApplicationHost.PopulateAddressSpaces</c>) can mark the subtree Faulted.
/// </summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task BuildAddressSpaceAsync_Propagates_Discovery_Exceptions_To_Caller()
{
@@ -169,33 +177,25 @@ public sealed class GenericDriverNodeManagerTests
/// <summary>Driver whose DiscoverAsync throws — exercises the exception-isolation boundary.</summary>
private sealed class ThrowingDiscoveryDriver : IDriver, ITagDiscovery
{
/// <summary>Gets the driver instance identifier.</summary>
/// <inheritdoc />
public string DriverInstanceId => "throwing";
/// <summary>Gets the driver type name.</summary>
/// <inheritdoc />
public string DriverType => "Throwing";
/// <summary>Initializes the driver with configuration.</summary>
/// <param name="_">Configuration JSON (unused in test double).</param>
/// <param name="__">Cancellation token (unused in test double).</param>
/// <inheritdoc />
public Task InitializeAsync(string _, CancellationToken __) => Task.CompletedTask;
/// <summary>Reinitializes the driver with new configuration.</summary>
/// <param name="_">Configuration JSON (unused in test double).</param>
/// <param name="__">Cancellation token (unused in test double).</param>
/// <inheritdoc />
public Task ReinitializeAsync(string _, CancellationToken __) => Task.CompletedTask;
/// <summary>Shuts down the driver.</summary>
/// <param name="_">Cancellation token (unused in test double).</param>
/// <inheritdoc />
public Task ShutdownAsync(CancellationToken _) => Task.CompletedTask;
/// <summary>Gets the current health status of the driver.</summary>
/// <inheritdoc />
public DriverHealth GetHealth() => new(DriverState.Healthy, null, null);
/// <summary>Gets the memory footprint of the driver.</summary>
/// <inheritdoc />
public long GetMemoryFootprint() => 0;
/// <summary>Flushes optional caches in the driver.</summary>
/// <param name="_">Cancellation token (unused in test double).</param>
/// <inheritdoc />
public Task FlushOptionalCachesAsync(CancellationToken _) => Task.CompletedTask;
/// <summary>Discovers the address space by throwing an exception.</summary>
/// <param name="builder">The builder used to construct the address space.</param>
/// <param name="ct">Cancellation token.</param>
/// <inheritdoc />
public Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken ct)
=> throw new InvalidOperationException("discovery boom");
}
@@ -204,35 +204,27 @@ public sealed class GenericDriverNodeManagerTests
private sealed class FakeDriver : IDriver, ITagDiscovery, IAlarmSource
{
/// <summary>Gets the driver instance identifier.</summary>
/// <inheritdoc />
public string DriverInstanceId => "fake";
/// <summary>Gets the driver type name.</summary>
/// <inheritdoc />
public string DriverType => "Fake";
/// <summary>Occurs when an alarm event is raised.</summary>
public event EventHandler<AlarmEventArgs>? OnAlarmEvent;
/// <summary>Initializes the driver with configuration.</summary>
/// <param name="driverConfigJson">Configuration JSON.</param>
/// <param name="ct">Cancellation token.</param>
/// <inheritdoc />
public Task InitializeAsync(string driverConfigJson, CancellationToken ct) => Task.CompletedTask;
/// <summary>Reinitializes the driver with new configuration.</summary>
/// <param name="driverConfigJson">Configuration JSON.</param>
/// <param name="ct">Cancellation token.</param>
/// <inheritdoc />
public Task ReinitializeAsync(string driverConfigJson, CancellationToken ct) => Task.CompletedTask;
/// <summary>Shuts down the driver.</summary>
/// <param name="ct">Cancellation token.</param>
/// <inheritdoc />
public Task ShutdownAsync(CancellationToken ct) => Task.CompletedTask;
/// <summary>Gets the current health status of the driver.</summary>
/// <inheritdoc />
public DriverHealth GetHealth() => new(DriverState.Healthy, DateTime.UtcNow, null);
/// <summary>Gets the memory footprint of the driver.</summary>
/// <inheritdoc />
public long GetMemoryFootprint() => 0;
/// <summary>Flushes optional caches in the driver.</summary>
/// <param name="ct">Cancellation token.</param>
/// <inheritdoc />
public Task FlushOptionalCachesAsync(CancellationToken ct) => Task.CompletedTask;
/// <summary>Discovers the address space and registers alarm conditions.</summary>
/// <param name="builder">The builder used to construct the address space.</param>
/// <param name="ct">Cancellation token.</param>
/// <inheritdoc />
public Task DiscoverAsync(IAddressSpaceBuilder builder, CancellationToken ct)
{
var folder = builder.Folder("Tank", "Tank");
@@ -253,25 +245,19 @@ public sealed class GenericDriverNodeManagerTests
/// <param name="args">The alarm event arguments.</param>
public void RaiseAlarm(AlarmEventArgs args) => OnAlarmEvent?.Invoke(this, args);
/// <summary>Subscribes to alarm events.</summary>
/// <param name="_">Tag references to subscribe to (unused in test double).</param>
/// <param name="__">Cancellation token (unused in test double).</param>
/// <inheritdoc />
public Task<IAlarmSubscriptionHandle> SubscribeAlarmsAsync(IReadOnlyList<string> _, CancellationToken __)
=> Task.FromResult<IAlarmSubscriptionHandle>(new FakeHandle("sub"));
/// <summary>Unsubscribes from alarm events.</summary>
/// <param name="_">The subscription handle (unused in test double).</param>
/// <param name="__">Cancellation token (unused in test double).</param>
/// <inheritdoc />
public Task UnsubscribeAlarmsAsync(IAlarmSubscriptionHandle _, CancellationToken __) => Task.CompletedTask;
/// <summary>Acknowledges alarm notifications.</summary>
/// <param name="_">Alarm acknowledgement requests (unused in test double).</param>
/// <param name="__">Cancellation token (unused in test double).</param>
/// <inheritdoc />
public Task AcknowledgeAsync(IReadOnlyList<AlarmAcknowledgeRequest> _, CancellationToken __) => Task.CompletedTask;
}
/// <summary>Test double for IAlarmSubscriptionHandle.</summary>
private sealed class FakeHandle(string diagnosticId) : IAlarmSubscriptionHandle
{
/// <summary>Gets the diagnostic identifier for this subscription.</summary>
/// <inheritdoc />
public string DiagnosticId { get; } = diagnosticId;
}
@@ -281,31 +267,22 @@ public sealed class GenericDriverNodeManagerTests
/// <summary>Gets the map of alarm sources to their sinks.</summary>
public Dictionary<string, RecordingSink> Alarms { get; } = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Creates a folder in the address space.</summary>
/// <param name="_">The contained name (unused in test double).</param>
/// <param name="__">The display name (unused in test double).</param>
/// <inheritdoc />
public IAddressSpaceBuilder Folder(string _, string __) => this;
/// <summary>Creates a variable in the address space.</summary>
/// <param name="_">The contained name (unused in test double).</param>
/// <param name="__">The display name (unused in test double).</param>
/// <param name="info">The driver attribute information.</param>
/// <inheritdoc />
public IVariableHandle Variable(string _, string __, DriverAttributeInfo info)
=> new Handle(info.FullName, Alarms);
/// <summary>Adds a property to the current variable.</summary>
/// <param name="_">The property name (unused in test double).</param>
/// <param name="__">The data type (unused in test double).</param>
/// <param name="___">The initial value (unused in test double).</param>
/// <inheritdoc />
public void AddProperty(string _, DriverDataType __, object? ___) { }
/// <summary>Test double for IVariableHandle.</summary>
public sealed class Handle(string fullRef, Dictionary<string, RecordingSink> alarms) : IVariableHandle
{
/// <summary>Gets the full reference name for this variable.</summary>
/// <inheritdoc />
public string FullReference { get; } = fullRef;
/// <summary>Marks this variable as an alarm condition and registers its sink.</summary>
/// <param name="_">The alarm condition info (unused in test double).</param>
/// <inheritdoc />
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo _)
{
var sink = new RecordingSink();
@@ -319,8 +296,7 @@ public sealed class GenericDriverNodeManagerTests
{
/// <summary>Gets the list of alarm transitions received by this sink.</summary>
public List<AlarmEventArgs> Received { get; } = new();
/// <summary>Records an alarm transition.</summary>
/// <param name="args">The alarm event arguments.</param>
/// <inheritdoc />
public void OnTransition(AlarmEventArgs args) => Received.Add(args);
}
}
@@ -12,6 +12,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Observability;
public sealed class CapabilityInvokerEnrichmentTests
{
/// <summary>Verifies that InvokerExecute logs inside call site with structured properties.</summary>
/// <returns>A task that represents the asynchronous test.</returns>
[Fact]
public async Task InvokerExecute_LogsInsideCallSite_CarryStructuredProperties()
{
@@ -45,6 +46,7 @@ public sealed class CapabilityInvokerEnrichmentTests
}
/// <summary>Verifies that InvokerExecute does not leak context outside the call site.</summary>
/// <returns>A task that represents the asynchronous test.</returns>
[Fact]
public async Task InvokerExecute_DoesNotLeak_ContextOutsideCallSite()
{
@@ -377,9 +377,7 @@ public sealed class EquipmentNodeWalkerTests
/// <summary>Gets the list of properties.</summary>
public List<RecordingProperty> Properties { get; } = new();
/// <summary>Creates a folder child node.</summary>
/// <param name="name">The browse name of the folder.</param>
/// <param name="_">The display name (unused).</param>
/// <inheritdoc />
public IAddressSpaceBuilder Folder(string name, string _)
{
var child = new RecordingBuilder(name);
@@ -387,10 +385,7 @@ public sealed class EquipmentNodeWalkerTests
return child;
}
/// <summary>Creates a variable node.</summary>
/// <param name="name">The browse name of the variable.</param>
/// <param name="_">The display name (unused).</param>
/// <param name="attr">The attribute information for the variable.</param>
/// <inheritdoc />
public IVariableHandle Variable(string name, string _, DriverAttributeInfo attr)
{
var v = new RecordingVariable(name, attr);
@@ -398,10 +393,7 @@ public sealed class EquipmentNodeWalkerTests
return v;
}
/// <summary>Adds a property to the node.</summary>
/// <param name="name">The browse name of the property.</param>
/// <param name="_">The data type (unused).</param>
/// <param name="value">The value of the property.</param>
/// <inheritdoc />
public void AddProperty(string name, DriverDataType _, object? value) =>
Properties.Add(new RecordingProperty(name, value));
}
@@ -412,10 +404,9 @@ public sealed class EquipmentNodeWalkerTests
/// <summary>Recorded variable for test verification.</summary>
private sealed record RecordingVariable(string BrowseName, DriverAttributeInfo AttributeInfo) : IVariableHandle
{
/// <summary>Gets the full reference of the variable.</summary>
/// <inheritdoc />
public string FullReference => AttributeInfo.FullName;
/// <summary>Marks the variable as an alarm condition.</summary>
/// <param name="info">The alarm condition information.</param>
/// <inheritdoc />
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => throw new NotSupportedException();
}
}
@@ -18,26 +18,18 @@ public sealed class IdentificationFolderBuilderTests
/// <summary>Gets or sets the list of added properties.</summary>
public List<(string BrowseName, DriverDataType DataType, object? Value)> Properties { get; } = [];
/// <summary>Records a folder and returns this builder for chaining.</summary>
/// <param name="browseName">The browse name of the folder.</param>
/// <param name="displayName">The display name of the folder.</param>
/// <inheritdoc />
public IAddressSpaceBuilder Folder(string browseName, string displayName)
{
Folders.Add((browseName, displayName));
return this; // flat recording — identification fields land in the same bucket
}
/// <summary>Not supported in test context.</summary>
/// <param name="browseName">The browse name of the variable.</param>
/// <param name="displayName">The display name of the variable.</param>
/// <param name="attributeInfo">The attribute information.</param>
/// <inheritdoc />
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
=> throw new NotSupportedException("Identification fields use AddProperty, not Variable");
/// <summary>Records a property addition.</summary>
/// <param name="browseName">The browse name of the property.</param>
/// <param name="dataType">The data type of the property.</param>
/// <param name="value">The property value.</param>
/// <inheritdoc />
public void AddProperty(string browseName, DriverDataType dataType, object? value)
=> Properties.Add((browseName, dataType, value));
}
@@ -11,6 +11,7 @@ public sealed class AlarmSurfaceInvokerTests
private static readonly DriverResilienceOptions TierAOptions = new() { Tier = DriverTier.A };
/// <summary>Verifies SubscribeAsync on an empty list returns empty without calling the driver.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SubscribeAsync_EmptyList_ReturnsEmpty_WithoutDriverCall()
{
@@ -24,6 +25,7 @@ public sealed class AlarmSurfaceInvokerTests
}
/// <summary>Verifies SubscribeAsync with no resolver routes through the default host.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SubscribeAsync_SingleHost_RoutesThroughDefaultHost()
{
@@ -38,6 +40,7 @@ public sealed class AlarmSurfaceInvokerTests
}
/// <summary>Verifies SubscribeAsync fans out correctly to multiple hosts based on resolver.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SubscribeAsync_MultiHost_FansOutByResolvedHost()
{
@@ -57,6 +60,7 @@ public sealed class AlarmSurfaceInvokerTests
}
/// <summary>Verifies AcknowledgeAsync does not retry on failure.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task AcknowledgeAsync_DoesNotRetry_OnFailure()
{
@@ -70,6 +74,7 @@ public sealed class AlarmSurfaceInvokerTests
}
/// <summary>Verifies SubscribeAsync retries on transient failures.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task SubscribeAsync_Retries_Transient_Failures()
{
@@ -87,6 +92,7 @@ public sealed class AlarmSurfaceInvokerTests
/// Verify by using a per-call resolver with two distinct hosts and checking which host
/// name reaches the driver's UnsubscribeAlarmsAsync.
/// </summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task UnsubscribeAsync_Routes_Through_Same_Host_As_Subscribe()
{
@@ -112,6 +118,7 @@ public sealed class AlarmSurfaceInvokerTests
}
/// <summary>Verifies UnsubscribeAsync with no resolver uses the default host.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task UnsubscribeAsync_SingleHost_UsesDefaultHost()
{
@@ -158,10 +165,7 @@ public sealed class AlarmSurfaceInvokerTests
/// <summary>Gets the source node IDs from the most recent SubscribeAlarmsAsync call.</summary>
public IReadOnlyList<string> LastSubscribedIds { get; private set; } = [];
/// <summary>Subscribes to alarms.</summary>
/// <param name="sourceNodeIds">The source node IDs to subscribe to.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>An alarm subscription handle.</returns>
/// <inheritdoc />
public Task<IAlarmSubscriptionHandle> SubscribeAlarmsAsync(
IReadOnlyList<string> sourceNodeIds, CancellationToken cancellationToken)
{
@@ -172,20 +176,14 @@ public sealed class AlarmSurfaceInvokerTests
return Task.FromResult<IAlarmSubscriptionHandle>(new StubHandle($"h-{SubscribeCallCount}"));
}
/// <summary>Unsubscribes from alarms.</summary>
/// <param name="handle">The subscription handle to unsubscribe.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A completed task.</returns>
/// <inheritdoc />
public Task UnsubscribeAlarmsAsync(IAlarmSubscriptionHandle handle, CancellationToken cancellationToken)
{
UnsubscribeCallCount++;
return Task.CompletedTask;
}
/// <summary>Acknowledges alarms.</summary>
/// <param name="acknowledgements">The alarm acknowledgements to process.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A completed task.</returns>
/// <inheritdoc />
public Task AcknowledgeAsync(
IReadOnlyList<AlarmAcknowledgeRequest> acknowledgements, CancellationToken cancellationToken)
{
@@ -194,7 +192,7 @@ public sealed class AlarmSurfaceInvokerTests
return Task.CompletedTask;
}
/// <summary>Occurs when an alarm event is raised.</summary>
/// <inheritdoc />
public event EventHandler<AlarmEventArgs>? OnAlarmEvent { add { } remove { } }
}
@@ -206,9 +204,7 @@ public sealed class AlarmSurfaceInvokerTests
/// <param name="map">The map of source node IDs to host names.</param>
private sealed class StubResolver(Dictionary<string, string> map) : IPerCallHostResolver
{
/// <summary>Resolves the host for the given full reference.</summary>
/// <param name="fullReference">The full reference to resolve.</param>
/// <returns>The resolved host name.</returns>
/// <inheritdoc />
public string ResolveHost(string fullReference) => map[fullReference];
}
}
@@ -14,6 +14,7 @@ public sealed class CapabilityInvokerTests
new(builder, "drv-test", () => options);
/// <summary>Verifies that the capability invoker returns the value from the call site.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Read_ReturnsValue_FromCallSite()
{
@@ -29,6 +30,7 @@ public sealed class CapabilityInvokerTests
}
/// <summary>Verifies that the capability invoker retries on transient failures.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Read_Retries_OnTransientFailure()
{
@@ -52,6 +54,7 @@ public sealed class CapabilityInvokerTests
}
/// <summary>Verifies that non-idempotent writes do not retry even when the policy has retries configured.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Write_NonIdempotent_DoesNotRetry_EvenWhenPolicyHasRetries()
{
@@ -85,6 +88,7 @@ public sealed class CapabilityInvokerTests
}
/// <summary>Verifies that idempotent writes retry when the policy has retries configured.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Write_Idempotent_Retries_WhenPolicyHasRetries()
{
@@ -116,6 +120,7 @@ public sealed class CapabilityInvokerTests
}
/// <summary>Verifies that writes do not retry when the policy has zero retries configured.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Write_Default_DoesNotRetry_WhenPolicyHasZeroRetries()
{
@@ -143,6 +148,7 @@ public sealed class CapabilityInvokerTests
}
/// <summary>Verifies that different hosts are honored independently in the resilience pipeline.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Execute_HonorsDifferentHosts_Independently()
{
@@ -161,6 +167,7 @@ public sealed class CapabilityInvokerTests
/// redundant options objects on the per-write hot path and creates a consistency hazard
/// where an Admin edit mid-call could observe two different snapshots.
/// </summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task ExecuteWriteAsync_NonIdempotent_Snapshots_Options_Once_Per_Call()
{
@@ -195,6 +202,7 @@ public sealed class CapabilityInvokerTests
/// two derived values (<c>with</c> base + <c>Resolve(Write)</c>) come from the same options
/// instance.
/// </summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task ExecuteWriteAsync_NonIdempotent_Uses_Consistent_Options_Snapshot()
{
@@ -13,6 +13,7 @@ public sealed class DriverResiliencePipelineBuilderTests
private static readonly DriverResilienceOptions TierAOptions = new() { Tier = DriverTier.A };
/// <summary>Verifies that read operations retry transient failures.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Read_Retries_Transient_Failures()
{
@@ -31,6 +32,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that write operations do not retry on failure.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Write_DoesNotRetry_OnFailure()
{
@@ -53,6 +55,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that alarm acknowledge operations do not retry on failure.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task AlarmAcknowledge_DoesNotRetry_OnFailure()
{
@@ -115,6 +118,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that a dead host does not open the breaker for a sibling host.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task DeadHost_DoesNotOpenBreaker_ForSiblingHost()
{
@@ -146,6 +150,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that the circuit breaker opens after the failure threshold on tier A.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task CircuitBreaker_Opens_AfterFailureThreshold_OnTierA()
{
@@ -171,6 +176,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that timeout cancels slow operations.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Timeout_Cancels_SlowOperation()
{
@@ -211,6 +217,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that cancellation is not retried.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Cancellation_IsNot_Retried()
{
@@ -232,6 +239,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that the tracker records failure on every retry.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Tracker_RecordsFailure_OnEveryRetry()
{
@@ -253,6 +261,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that the tracker stamps the breaker open when it trips.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Tracker_StampsBreakerOpen_WhenBreakerTrips()
{
@@ -277,6 +286,7 @@ public sealed class DriverResiliencePipelineBuilderTests
}
/// <summary>Verifies that the tracker isolates counters per host.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Tracker_IsolatesCounters_PerHost()
{
@@ -15,6 +15,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Resilience;
public sealed class FlakeyDriverIntegrationTests
{
/// <summary>Verifies read succeeds after transient failures with retries.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Read_SurfacesSuccess_AfterTransientFailures()
{
@@ -43,6 +44,7 @@ public sealed class FlakeyDriverIntegrationTests
}
/// <summary>Verifies non-idempotent write fails on first failure without replay.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Write_NonIdempotent_FailsOnFirstFailure_NoReplay()
{
@@ -68,6 +70,7 @@ public sealed class FlakeyDriverIntegrationTests
}
/// <summary>Verifies idempotent write retries until success.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task Write_Idempotent_RetriesUntilSuccess()
{
@@ -93,6 +96,7 @@ public sealed class FlakeyDriverIntegrationTests
}
/// <summary>Verifies multiple hosts have independent failure counts and circuit breakers.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task MultipleHosts_OnOneDriver_HaveIndependentFailureCounts()
{
@@ -141,10 +145,7 @@ public sealed class FlakeyDriverIntegrationTests
_failWritesBeforeIndex = failWritesBeforeIndex;
}
/// <summary>Reads values, failing transiently until the threshold.</summary>
/// <param name="fullReferences">Full references to read.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Data value snapshots.</returns>
/// <inheritdoc />
public Task<IReadOnlyList<DataValueSnapshot>> ReadAsync(
IReadOnlyList<string> fullReferences,
CancellationToken cancellationToken)
@@ -160,10 +161,7 @@ public sealed class FlakeyDriverIntegrationTests
return Task.FromResult(result);
}
/// <summary>Writes values, failing transiently until the threshold.</summary>
/// <param name="writes">The write requests.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>Write results.</returns>
/// <inheritdoc />
public Task<IReadOnlyList<WriteResult>> WriteAsync(
IReadOnlyList<WriteRequest> writes,
CancellationToken cancellationToken)
@@ -69,6 +69,7 @@ public sealed class InFlightCounterTests
}
/// <summary>Verifies that CapabilityInvoker increments the tracker during execution.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task CapabilityInvoker_IncrementsTracker_DuringExecution()
{
@@ -97,6 +98,7 @@ public sealed class InFlightCounterTests
}
/// <summary>Verifies that CapabilityInvoker decrements the counter on exception.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task CapabilityInvoker_ExceptionPath_DecrementsCounter()
{
@@ -119,6 +121,7 @@ public sealed class InFlightCounterTests
}
/// <summary>Verifies that CapabilityInvoker without a tracker does not throw.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task CapabilityInvoker_WithoutTracker_DoesNotThrow()
{
@@ -22,13 +22,13 @@ public sealed class PerCallHostResolverDispatchTests
/// <param name="map">The mapping of full references to host names.</param>
public StaticResolver(Dictionary<string, string> map) => _map = map;
/// <summary>Resolves a host name from the static mapping.</summary>
/// <param name="fullReference">The full reference to resolve.</param>
/// <inheritdoc />
public string ResolveHost(string fullReference) =>
_map.TryGetValue(fullReference, out var host) ? host : string.Empty;
}
/// <summary>Verifies that a dead PLC does not open the breaker for healthy PLCs when using a per-call resolver.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task DeadPlc_DoesNotOpenBreaker_For_HealthyPlc_With_Resolver()
{
@@ -80,6 +80,7 @@ public sealed class PerCallHostResolverDispatchTests
}
/// <summary>Verifies that without a resolver, the same host shares one resilience pipeline.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task WithoutResolver_SameHost_Shares_One_Pipeline()
{
@@ -98,6 +99,7 @@ public sealed class PerCallHostResolverDispatchTests
}
/// <summary>Verifies that with a resolver, different hosts get separate resilience pipelines.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task WithResolver_TwoHosts_Get_Two_Pipelines()
{
@@ -10,6 +10,7 @@ namespace ZB.MOM.WW.OtOpcUa.Core.Tests.Stability;
public sealed class MemoryRecycleTests
{
/// <summary>Verifies that Tier C hard memory breach requests supervisor recycle.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task TierC_HardBreach_RequestsSupervisorRecycle()
{
@@ -25,6 +26,7 @@ public sealed class MemoryRecycleTests
/// <summary>Verifies that Tier A and B hard memory breach never request recycle.</summary>
/// <param name="tier">The driver tier to test.</param>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Theory]
[InlineData(DriverTier.A)]
[InlineData(DriverTier.B)]
@@ -40,6 +42,7 @@ public sealed class MemoryRecycleTests
}
/// <summary>Verifies that Tier C without supervisor hard breach is a no-op.</summary>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Fact]
public async Task TierC_WithoutSupervisor_HardBreach_NoOp()
{
@@ -52,6 +55,7 @@ public sealed class MemoryRecycleTests
/// <summary>Verifies that soft memory breach never requests recycle at any tier.</summary>
/// <param name="tier">The driver tier to test.</param>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Theory]
[InlineData(DriverTier.A)]
[InlineData(DriverTier.B)]
@@ -69,6 +73,7 @@ public sealed class MemoryRecycleTests
/// <summary>Verifies that non-breach memory actions are no-ops.</summary>
/// <param name="action">The non-breach memory tracking action to test.</param>
/// <returns>A task that represents the asynchronous test operation.</returns>
[Theory]
[InlineData(MemoryTrackingAction.None)]
[InlineData(MemoryTrackingAction.Warming)]
@@ -85,16 +90,14 @@ public sealed class MemoryRecycleTests
private sealed class FakeSupervisor : IDriverSupervisor
{
/// <summary>Gets the driver instance identifier.</summary>
/// <inheritdoc />
public string DriverInstanceId => "fake-tier-c";
/// <summary>Gets the count of recycle operations.</summary>
public int RecycleCount { get; private set; }
/// <summary>Gets the reason from the last recycle operation.</summary>
public string? LastReason { get; private set; }
/// <summary>Recycles the driver asynchronously.</summary>
/// <param name="reason">The reason for recycling.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <inheritdoc />
public Task RecycleAsync(string reason, CancellationToken cancellationToken)
{
RecycleCount++;
@@ -36,6 +36,7 @@ public sealed class ScheduledRecycleSchedulerTests
}
/// <summary>Verifies Tick before the next recycle time is a no-op.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Tick_BeforeNextRecycle_NoOp()
{
@@ -49,6 +50,7 @@ public sealed class ScheduledRecycleSchedulerTests
}
/// <summary>Verifies Tick at or after the next recycle time fires once and advances.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task Tick_AtOrAfterNextRecycle_FiresOnce_AndAdvances()
{
@@ -63,6 +65,7 @@ public sealed class ScheduledRecycleSchedulerTests
}
/// <summary>Verifies RequestRecycleNow fires immediately without advancing the schedule.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task RequestRecycleNow_Fires_Immediately_WithoutAdvancingSchedule()
{
@@ -78,6 +81,7 @@ public sealed class ScheduledRecycleSchedulerTests
}
/// <summary>Verifies multiple ticks across the recycle interval each advance by one interval.</summary>
/// <returns>A task that represents the asynchronous operation.</returns>
[Fact]
public async Task MultipleFires_AcrossTicks_AdvanceOneIntervalEach()
{
@@ -95,7 +99,7 @@ public sealed class ScheduledRecycleSchedulerTests
/// <summary>Fake driver supervisor for testing.</summary>
private sealed class FakeSupervisor : IDriverSupervisor
{
/// <summary>Gets the driver instance ID.</summary>
/// <inheritdoc />
public string DriverInstanceId => "tier-c-fake";
/// <summary>Gets the number of times RecycleAsync was called.</summary>
@@ -104,10 +108,7 @@ public sealed class ScheduledRecycleSchedulerTests
/// <summary>Gets the reason from the most recent recycle call.</summary>
public string? LastReason { get; private set; }
/// <summary>Simulates a driver recycle operation.</summary>
/// <param name="reason">The reason for the recycle.</param>
/// <param name="cancellationToken">Cancellation token.</param>
/// <returns>A completed task.</returns>
/// <inheritdoc />
public Task RecycleAsync(string reason, CancellationToken cancellationToken)
{
RecycleCount++;