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:
@@ -120,6 +120,7 @@ public sealed class DeployWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that bootstrap deploy events are suppressed.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task BootstrapEventIsSuppressed()
|
||||
{
|
||||
@@ -143,6 +144,7 @@ public sealed class DeployWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a deployment time change fires a rediscovery event.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DeployTimeChangeFiresRediscover()
|
||||
{
|
||||
@@ -171,6 +173,7 @@ public sealed class DeployWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the same deployment time does not fire a rediscovery event.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SameDeployTimeDoesNotFire()
|
||||
{
|
||||
@@ -195,6 +198,7 @@ public sealed class DeployWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a deployment time presence flip fires a rediscovery event.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task TimeOfLastDeployPresentFlipFiresRediscover()
|
||||
{
|
||||
@@ -222,6 +226,7 @@ public sealed class DeployWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that stop cancels the watcher loop cleanly.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StopCancelsLoopCleanly()
|
||||
{
|
||||
@@ -243,6 +248,7 @@ public sealed class DeployWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disposing stops a running watcher.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisposeStopsRunningWatcher()
|
||||
{
|
||||
@@ -262,6 +268,7 @@ public sealed class DeployWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a source exception triggers retry with backoff.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SourceExceptionTriggersRetryWithBackoff()
|
||||
{
|
||||
|
||||
+20
-35
@@ -16,9 +16,7 @@ public sealed class GalaxyDiscovererTests
|
||||
{
|
||||
private sealed class FakeHierarchySource(IReadOnlyList<GalaxyObject> objects) : IGalaxyHierarchySource
|
||||
{
|
||||
/// <summary>Gets the hierarchy asynchronously from the fake source.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that returns the pre-built Galaxy object list.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<GalaxyObject>> GetHierarchyAsync(CancellationToken cancellationToken)
|
||||
=> Task.FromResult(objects);
|
||||
}
|
||||
@@ -41,10 +39,7 @@ public sealed class GalaxyDiscovererTests
|
||||
public FakeBuilder() : this(null) { }
|
||||
private FakeBuilder(string? folder) { _currentFolder = folder; }
|
||||
|
||||
/// <summary>Adds a folder call to the recorded list.</summary>
|
||||
/// <param name="browseName">The browse name for the folder.</param>
|
||||
/// <param name="displayName">The display name for the folder.</param>
|
||||
/// <returns>An IAddressSpaceBuilder scoped to the new folder.</returns>
|
||||
/// <inheritdoc />
|
||||
public IAddressSpaceBuilder Folder(string browseName, string displayName)
|
||||
{
|
||||
Folders.Add(new FolderCall(browseName, displayName));
|
||||
@@ -52,11 +47,7 @@ public sealed class GalaxyDiscovererTests
|
||||
return new ChildBuilder(this, browseName);
|
||||
}
|
||||
|
||||
/// <summary>Adds a variable call to the recorded list.</summary>
|
||||
/// <param name="browseName">The browse name for the variable.</param>
|
||||
/// <param name="displayName">The display name for the variable.</param>
|
||||
/// <param name="attributeInfo">The attribute metadata for the variable.</param>
|
||||
/// <returns>An IVariableHandle for further configuration.</returns>
|
||||
/// <inheritdoc />
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
|
||||
{
|
||||
var folder = _currentFolder ?? "<root>";
|
||||
@@ -64,51 +55,36 @@ public sealed class GalaxyDiscovererTests
|
||||
return new FakeVariableHandle(this, attributeInfo.FullName);
|
||||
}
|
||||
|
||||
/// <summary>Adds a property call to the builder (not recorded in this fake).</summary>
|
||||
/// <param name="browseName">The browse name for the property.</param>
|
||||
/// <param name="dataType">The driver data type of the property.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <inheritdoc />
|
||||
public void AddProperty(string browseName, DriverDataType dataType, object? value) { }
|
||||
|
||||
/// <summary>Child folder routes Variable calls back to the parent's lists with its own scope.</summary>
|
||||
private sealed class ChildBuilder(FakeBuilder parent, string folderBrowseName) : IAddressSpaceBuilder
|
||||
{
|
||||
/// <summary>Adds a child folder call to the parent builder's recorded list.</summary>
|
||||
/// <param name="browseName">The browse name for the folder.</param>
|
||||
/// <param name="displayName">The display name for the folder.</param>
|
||||
/// <returns>An IAddressSpaceBuilder scoped to the new child folder.</returns>
|
||||
/// <inheritdoc />
|
||||
public IAddressSpaceBuilder Folder(string browseName, string displayName)
|
||||
{
|
||||
parent.Folders.Add(new FolderCall(browseName, displayName));
|
||||
return new ChildBuilder(parent, browseName);
|
||||
}
|
||||
|
||||
/// <summary>Adds a variable call to the parent builder's recorded list, scoped to this folder.</summary>
|
||||
/// <param name="browseName">The browse name for the variable.</param>
|
||||
/// <param name="displayName">The display name for the variable.</param>
|
||||
/// <param name="attributeInfo">The attribute metadata for the variable.</param>
|
||||
/// <returns>An IVariableHandle for further configuration.</returns>
|
||||
/// <inheritdoc />
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
|
||||
{
|
||||
parent.Variables.Add(new VariableCall(folderBrowseName, browseName, attributeInfo));
|
||||
return new FakeVariableHandle(parent, attributeInfo.FullName);
|
||||
}
|
||||
|
||||
/// <summary>Adds a property call to the builder (not recorded in this fake).</summary>
|
||||
/// <param name="browseName">The browse name for the property.</param>
|
||||
/// <param name="dataType">The driver data type of the property.</param>
|
||||
/// <param name="value">The property value.</param>
|
||||
/// <inheritdoc />
|
||||
public void AddProperty(string browseName, DriverDataType dataType, object? value) { }
|
||||
}
|
||||
|
||||
private sealed class FakeVariableHandle(FakeBuilder owner, string fullRef) : IVariableHandle
|
||||
{
|
||||
/// <summary>Gets the full reference for this variable.</summary>
|
||||
/// <inheritdoc />
|
||||
public string FullReference { get; } = fullRef;
|
||||
|
||||
/// <summary>Marks this variable as an alarm condition and records it.</summary>
|
||||
/// <param name="info">The alarm condition metadata.</param>
|
||||
/// <returns>An IAlarmConditionSink for further alarm configuration.</returns>
|
||||
/// <inheritdoc />
|
||||
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info)
|
||||
{
|
||||
owner.AlarmDeclarations[FullReference] = info;
|
||||
@@ -118,8 +94,7 @@ public sealed class GalaxyDiscovererTests
|
||||
|
||||
private sealed class NoopSink : IAlarmConditionSink
|
||||
{
|
||||
/// <summary>Records an alarm transition event (no-op in this fake).</summary>
|
||||
/// <param name="args">The alarm event arguments.</param>
|
||||
/// <inheritdoc />
|
||||
public void OnTransition(AlarmEventArgs args) { }
|
||||
}
|
||||
}
|
||||
@@ -156,6 +131,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that discovery creates one folder per object and one variable per attribute.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_BuildsOneFolderPerObject_AndOneVariablePerAttribute()
|
||||
{
|
||||
@@ -178,6 +154,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that full reference defaults to tag.attribute format when not explicitly supplied.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_FullReference_DefaultsToTagDotAttribute()
|
||||
{
|
||||
@@ -193,6 +170,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that full reference uses gateway-supplied value when provided.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_FullReference_PrefersGwSuppliedFullTagReference()
|
||||
{
|
||||
@@ -208,6 +186,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that browse name falls back to tag name when contained name is empty.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_BrowseName_FallsBackToTagName_WhenContainedEmpty()
|
||||
{
|
||||
@@ -223,6 +202,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that attribute metadata fields are all propagated to the discovered variable.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_AttributeMetadata_PropagatesEveryField()
|
||||
{
|
||||
@@ -248,6 +228,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that alarm attributes populate all five sub-attribute references.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_AlarmAttribute_PopulatesAllFiveSubAttributeRefs()
|
||||
{
|
||||
@@ -270,6 +251,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that non-alarm attributes are not marked as alarm conditions.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_NonAlarmAttribute_DoesNotMarkCondition()
|
||||
{
|
||||
@@ -287,6 +269,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that objects with empty identity are skipped during discovery.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_SkipsObjectsWithEmptyIdentity()
|
||||
{
|
||||
@@ -304,6 +287,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that attributes with empty names are skipped during discovery.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DiscoverAsync_SkipsAttributesWithEmptyName()
|
||||
{
|
||||
@@ -320,6 +304,7 @@ public sealed class GalaxyDiscovererTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that driver discovery routes through the injected hierarchy source.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DriverDiscoverAsync_RoutesThroughInjectedSource()
|
||||
{
|
||||
|
||||
+4
-3
@@ -17,6 +17,7 @@ public sealed class GalaxyDriverAlarmEventArgsExtensionTests
|
||||
/// <summary>
|
||||
/// Verifies that acknowledge transition with full payload populates extended fields.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Acknowledge_transition_with_full_payload_populates_extended_fields()
|
||||
{
|
||||
@@ -53,6 +54,7 @@ public sealed class GalaxyDriverAlarmEventArgsExtensionTests
|
||||
/// <summary>
|
||||
/// Verifies that raise transition without optional fields leaves them null.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Raise_transition_without_optional_fields_leaves_them_null()
|
||||
{
|
||||
@@ -118,9 +120,8 @@ public sealed class GalaxyDriverAlarmEventArgsExtensionTests
|
||||
public void Emit(GalaxyAlarmTransition transition)
|
||||
=> OnAlarmTransition?.Invoke(this, transition);
|
||||
|
||||
/// <summary>
|
||||
/// Disposes the alarm feed asynchronously.
|
||||
/// </summary>
|
||||
/// <summary>Disposes the alarm feed asynchronously.</summary>
|
||||
/// <returns>A completed value task.</returns>
|
||||
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests;
|
||||
public sealed class GalaxyDriverAlarmSourceTests
|
||||
{
|
||||
/// <summary>Verifies that SubscribeAlarmsAsync starts the alarm feed and events fire on transition.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAlarmsAsync_starts_feed_and_event_fires_on_transition()
|
||||
{
|
||||
@@ -62,6 +63,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UnsubscribeAlarmsAsync stops event flow.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task UnsubscribeAlarmsAsync_stops_event_flow()
|
||||
{
|
||||
@@ -84,6 +86,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that UnsubscribeAlarmsAsync throws for a foreign handle.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task UnsubscribeAlarmsAsync_throws_for_foreign_handle()
|
||||
{
|
||||
@@ -97,6 +100,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AcknowledgeAsync routes each request to the acknowledger.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task AcknowledgeAsync_routes_each_request_to_the_acknowledger()
|
||||
{
|
||||
@@ -119,6 +123,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AcknowledgeAsync falls back to SourceNodeId when ConditionId is empty.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task AcknowledgeAsync_falls_back_to_SourceNodeId_when_ConditionId_empty()
|
||||
{
|
||||
@@ -134,6 +139,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that AcknowledgeAsync throws NotSupportedException without an acknowledger.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task AcknowledgeAsync_throws_NotSupported_without_acknowledger()
|
||||
{
|
||||
@@ -191,7 +197,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
/// <summary>Occurs when an alarm transition is emitted.</summary>
|
||||
public event EventHandler<GalaxyAlarmTransition>? OnAlarmTransition;
|
||||
|
||||
/// <summary>Marks the feed as started.</summary>
|
||||
/// <inheritdoc />
|
||||
public void Start() => Started = true;
|
||||
|
||||
/// <summary>Emits an alarm transition to all subscribers.</summary>
|
||||
@@ -210,12 +216,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
/// <summary>Gets the list of acknowledge calls recorded.</summary>
|
||||
public List<(string AlarmRef, string Comment, string Operator)> Calls { get; } = [];
|
||||
|
||||
/// <summary>Records an acknowledge call.</summary>
|
||||
/// <param name="alarmFullReference">The alarm full reference.</param>
|
||||
/// <param name="comment">The acknowledgment comment.</param>
|
||||
/// <param name="operatorUser">The operator user.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task AcknowledgeAsync(string alarmFullReference, string comment, string operatorUser, CancellationToken cancellationToken)
|
||||
{
|
||||
Calls.Add((alarmFullReference, comment, operatorUser));
|
||||
@@ -226,7 +227,7 @@ public sealed class GalaxyDriverAlarmSourceTests
|
||||
/// <summary>Test double that represents a foreign alarm subscription handle.</summary>
|
||||
private sealed class ForeignAlarmHandle : IAlarmSubscriptionHandle
|
||||
{
|
||||
/// <summary>Gets the diagnostic ID for this handle.</summary>
|
||||
/// <inheritdoc />
|
||||
public string DiagnosticId => "foreign";
|
||||
}
|
||||
}
|
||||
|
||||
+14
-3
@@ -130,13 +130,24 @@ public sealed class GalaxyDriverApiKeyResolverTests
|
||||
/// <summary>Gets the list of captured log entries with their levels and messages.</summary>
|
||||
public List<(LogLevel Level, string Message)> Entries { get; } = new();
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Begins a logging scope (returns null — no scope support in test logger).</summary>
|
||||
/// <typeparam name="TState">The type of the state object.</typeparam>
|
||||
/// <param name="state">The state object.</param>
|
||||
/// <returns>Null — test logger has no scope support.</returns>
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => null;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Returns true for all log levels so all messages are captured.</summary>
|
||||
/// <param name="logLevel">The log level to check.</param>
|
||||
/// <returns>Always true.</returns>
|
||||
public bool IsEnabled(LogLevel logLevel) => true;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Captures the log message and level into the Entries list.</summary>
|
||||
/// <typeparam name="TState">The type of the state object.</typeparam>
|
||||
/// <param name="logLevel">The log level.</param>
|
||||
/// <param name="eventId">The event ID.</param>
|
||||
/// <param name="state">The state object.</param>
|
||||
/// <param name="exception">The exception, if any.</param>
|
||||
/// <param name="formatter">The formatter function.</param>
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
|
||||
=> Entries.Add((logLevel, formatter(state, exception)));
|
||||
}
|
||||
|
||||
@@ -125,6 +125,7 @@ public sealed class GalaxyDriverFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that driver lifecycle toggles health state on initialize and shutdown.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task DriverLifecycle_InitializeShutdown_ToggleHealth()
|
||||
{
|
||||
@@ -148,6 +149,7 @@ public sealed class GalaxyDriverFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reinitializing with equivalent config refreshes health.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReinitializeAsync_RefreshesHealth_WhenConfigIsEquivalent()
|
||||
{
|
||||
@@ -218,6 +220,7 @@ public sealed class GalaxyDriverFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that initializing after dispose throws an exception.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task InitializeAfterDispose_Throws()
|
||||
{
|
||||
@@ -244,6 +247,7 @@ public sealed class GalaxyDriverFactoryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that GetHostStatuses returns an empty snapshot after initialization with seam.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetHostStatuses_AfterInitWithSeam_ReturnsEmptySnapshot()
|
||||
{
|
||||
|
||||
+14
-18
@@ -34,6 +34,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that memory footprint is nonzero after subscriptions are active.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetMemoryFootprint_IsNonZeroAfterSubscribe()
|
||||
{
|
||||
@@ -48,6 +49,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that memory footprint decreases after unsubscribing.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task GetMemoryFootprint_DecreasesAfterUnsubscribe()
|
||||
{
|
||||
@@ -67,6 +69,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
// ===== Driver.Galaxy-007 regression: Dispose cancels the dispose CTS =====
|
||||
|
||||
/// <summary>Verifies that Dispose sets disposed flag and blocks further capability calls.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Dispose_SetsDisposedFlag_BlockingFurtherCapabilityCalls()
|
||||
{
|
||||
@@ -81,6 +84,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that DisposeAsync can be awaited without deadlock.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task DisposeAsync_CanBeAwaitedWithoutDeadlock()
|
||||
{
|
||||
@@ -93,6 +97,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
// ===== Driver.Galaxy-013 regression: ReplayOnSessionLost gates the replay step =====
|
||||
|
||||
/// <summary>Verifies that ReplayOnSessionLost=false skips resubscription on reconnect.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReplayOnSessionLost_False_SkipsResubscribeBulk()
|
||||
{
|
||||
@@ -123,6 +128,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReplayOnSessionLost=true runs resubscription on reconnect.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReplayOnSessionLost_True_RunsResubscribeBulk()
|
||||
{
|
||||
@@ -152,10 +158,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
/// <summary>Gets the count of subscription calls.</summary>
|
||||
public int SubscribeCalls;
|
||||
|
||||
/// <summary>Subscribes to multiple tags and counts the call.</summary>
|
||||
/// <param name="fullReferences">List of tag addresses to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">Buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -169,14 +172,11 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
return Task.FromResult<IReadOnlyList<SubscribeResult>>(results);
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from multiple tags.</summary>
|
||||
/// <param name="itemHandles">List of subscription handles to unsubscribe.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>Streams subscription events.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
=> _stream.Reader.ReadAllAsync(cancellationToken);
|
||||
}
|
||||
@@ -184,6 +184,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
// ===== Driver.Galaxy-013 regression: ReinitializeAsync rejects unsupported reapply =====
|
||||
|
||||
/// <summary>Verifies that ReinitializeAsync rejects non-equivalent config changes.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReinitializeAsync_RejectsNonEquivalentConfigChange()
|
||||
{
|
||||
@@ -203,6 +204,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReinitializeAsync accepts equivalent config.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReinitializeAsync_AcceptsEquivalentConfig()
|
||||
{
|
||||
@@ -227,10 +229,7 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
{
|
||||
private readonly Channel<MxEvent> _stream = Channel.CreateUnbounded<MxEvent>();
|
||||
|
||||
/// <summary>Subscribes to multiple tags (no-op).</summary>
|
||||
/// <param name="fullReferences">List of tag addresses to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">Buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -243,14 +242,11 @@ public sealed class GalaxyDriverInfrastructureTests
|
||||
return Task.FromResult<IReadOnlyList<SubscribeResult>>(results);
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from multiple tags (no-op).</summary>
|
||||
/// <param name="itemHandles">List of subscription handles to unsubscribe.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>Streams subscription events (no-op).</summary>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
=> _stream.Reader.ReadAllAsync(cancellationToken);
|
||||
}
|
||||
|
||||
+12
-9
@@ -27,10 +27,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
/// <summary>Gets a mapping of tag addresses to their assigned item handles.</summary>
|
||||
public Dictionary<string, int> HandleByAddress { get; } = new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>Simulates a bulk subscribe operation by generating handles for each reference.</summary>
|
||||
/// <param name="fullReferences">The list of tag addresses to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">The cancellation token for the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -51,17 +48,14 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
return Task.FromResult<IReadOnlyList<SubscribeResult>>(results);
|
||||
}
|
||||
|
||||
/// <summary>Simulates a bulk unsubscribe operation by recording the handles.</summary>
|
||||
/// <param name="itemHandles">The list of item handles to unsubscribe.</param>
|
||||
/// <param name="cancellationToken">The cancellation token for the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
{
|
||||
Unsubscribes.Add([.. itemHandles]);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Returns an empty event stream for testing.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token for the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
=> Empty();
|
||||
|
||||
@@ -73,6 +67,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that syncing platforms subscribes to the ScanState address for each platform.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SyncPlatformsAsync_SubscribesScanStateAddressForEachPlatform()
|
||||
{
|
||||
@@ -88,6 +83,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the default buffered interval is zero, matching gateway cadence.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SyncPlatformsAsync_DefaultBufferedIntervalIsZero_GwCadence()
|
||||
{
|
||||
@@ -100,6 +96,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a configured buffered interval is forwarded to the gateway.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SyncPlatformsAsync_ConfiguredBufferedInterval_IsForwardedToGw()
|
||||
{
|
||||
@@ -125,6 +122,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that syncing the same platform set twice does not resubscribe.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SyncPlatformsAsync_SameSetTwice_DoesNotResubscribe()
|
||||
{
|
||||
@@ -139,6 +137,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that removed platforms are unsubscribed and dropped from the aggregator.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task SyncPlatformsAsync_RemovedPlatforms_AreUnsubscribed_AndDroppedFromAggregator()
|
||||
{
|
||||
@@ -183,6 +182,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a running probe value is routed to the aggregator.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task OnProbeValueChanged_Running_RoutesToAggregator()
|
||||
{
|
||||
@@ -198,6 +198,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a probe value with bad quality routes as unknown state.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task OnProbeValueChanged_BadQuality_RoutesUnknown()
|
||||
{
|
||||
@@ -212,6 +213,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that foreign probe references are silently dropped.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task OnProbeValueChanged_ForeignReference_IsSilentlyDropped()
|
||||
{
|
||||
@@ -231,6 +233,7 @@ public sealed class PerPlatformProbeWatcherTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that dispose unsubscribes all tracked platforms.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Dispose_UnsubscribesAllTrackedPlatforms()
|
||||
{
|
||||
|
||||
+6
-12
@@ -18,6 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime;
|
||||
public sealed class EventPumpBoundedChannelTests
|
||||
{
|
||||
/// <summary>Verifies that the event pump drops newest events when the bounded channel fills and records metrics for dropped events.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drops_newest_when_channel_fills_and_records_metric()
|
||||
{
|
||||
@@ -68,6 +69,7 @@ public sealed class EventPumpBoundedChannelTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the event pump throws an exception when the channel capacity is invalid.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Throws_when_channelCapacity_is_invalid()
|
||||
{
|
||||
@@ -81,6 +83,7 @@ public sealed class EventPumpBoundedChannelTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that event pump metrics are tagged with the client name for tracking multiple driver hosts.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Tags_metrics_with_client_name_for_multi_driver_hosts()
|
||||
{
|
||||
@@ -189,25 +192,16 @@ public sealed class EventPumpBoundedChannelTests
|
||||
private readonly Channel<MxEvent> _stream =
|
||||
Channel.CreateUnbounded<MxEvent>(new UnboundedChannelOptions { SingleReader = true });
|
||||
|
||||
/// <summary>Subscribes to a bulk list of tag references.</summary>
|
||||
/// <param name="fullReferences">The list of full references to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>An empty result list.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
=> Task.FromResult<IReadOnlyList<SubscribeResult>>([]);
|
||||
|
||||
/// <summary>Unsubscribes from a bulk list of item handles.</summary>
|
||||
/// <param name="itemHandles">The list of item handles to unsubscribe from.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>Streams events asynchronously.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>An async enumerable of MxEvent objects.</returns>
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
=> _stream.Reader.ReadAllAsync(cancellationToken);
|
||||
|
||||
|
||||
+11
-18
@@ -19,6 +19,7 @@ public sealed class EventPumpStreamFaultTests
|
||||
private const int WaitMs = 2_000;
|
||||
|
||||
/// <summary>Verifies that stream fault invokes the callback with the exception.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StreamFault_InvokesOnStreamFaultCallback_WithTheCause()
|
||||
{
|
||||
@@ -42,6 +43,7 @@ public sealed class EventPumpStreamFaultTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that stream fault drives the reconnect supervisor through reopen and replay.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task StreamFault_DrivesReconnectSupervisorReopenReplay()
|
||||
{
|
||||
@@ -79,6 +81,7 @@ public sealed class EventPumpStreamFaultTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that a faulted pump cannot be restarted in place, but a fresh pump resumes dispatch.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task FaultedPump_IsNotRestartableInPlace_ButAFreshPumpResumesDispatch()
|
||||
{
|
||||
@@ -122,6 +125,7 @@ public sealed class EventPumpStreamFaultTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that clean shutdown does not invoke the stream fault callback.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task CleanShutdown_DoesNotInvokeOnStreamFault()
|
||||
{
|
||||
@@ -150,22 +154,16 @@ public sealed class EventPumpStreamFaultTests
|
||||
private readonly Channel<MxEvent> _stream =
|
||||
Channel.CreateUnbounded<MxEvent>(new UnboundedChannelOptions { SingleReader = true });
|
||||
|
||||
/// <summary>Subscribes to multiple tags (test stub).</summary>
|
||||
/// <param name="fullReferences">The tag references to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
=> Task.FromResult<IReadOnlyList<SubscribeResult>>([]);
|
||||
|
||||
/// <summary>Unsubscribes from multiple tags (test stub).</summary>
|
||||
/// <param name="itemHandles">The item handles to unsubscribe from.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>Streams events asynchronously (test stub).</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
=> _stream.Reader.ReadAllAsync(cancellationToken);
|
||||
|
||||
@@ -183,28 +181,23 @@ public sealed class EventPumpStreamFaultTests
|
||||
private readonly Channel<MxEvent> _stream =
|
||||
Channel.CreateUnbounded<MxEvent>(new UnboundedChannelOptions { SingleReader = true });
|
||||
|
||||
/// <summary>Subscribes to multiple tags (test stub).</summary>
|
||||
/// <param name="fullReferences">The tag references to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
=> Task.FromResult<IReadOnlyList<SubscribeResult>>([]);
|
||||
|
||||
/// <summary>Unsubscribes from multiple tags (test stub).</summary>
|
||||
/// <param name="itemHandles">The item handles to unsubscribe from.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
/// <summary>Streams events asynchronously (test stub).</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
=> _stream.Reader.ReadAllAsync(cancellationToken);
|
||||
|
||||
/// <summary>Emits a data change event asynchronously.</summary>
|
||||
/// <param name="itemHandle">The item handle for the data change.</param>
|
||||
/// <param name="value">The numeric value of the change.</param>
|
||||
/// <returns>A value task that represents the asynchronous operation.</returns>
|
||||
public ValueTask EmitAsync(int itemHandle, double value) =>
|
||||
_stream.Writer.WriteAsync(new MxEvent
|
||||
{
|
||||
|
||||
@@ -43,6 +43,7 @@ public sealed class GalaxyDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadAsync routes through the injected reader.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_RoutesThroughInjectedReader()
|
||||
{
|
||||
@@ -58,6 +59,7 @@ public sealed class GalaxyDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadAsync returns empty without calling the reader for an empty request.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_EmptyRequest_ReturnsEmpty_WithoutCallingReader()
|
||||
{
|
||||
@@ -71,6 +73,7 @@ public sealed class GalaxyDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadAsync throws when seams and production runtime are not built.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_NoSeams_AndNoProductionRuntime_Throws()
|
||||
{
|
||||
@@ -86,6 +89,7 @@ public sealed class GalaxyDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadAsync throws after the driver is disposed.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_AfterDispose_Throws()
|
||||
{
|
||||
@@ -96,6 +100,7 @@ public sealed class GalaxyDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadAsync resolves from the first OnDataChange event on the subscribe-once path.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_SubscribeOncePath_ResolvesFromFirstOnDataChange()
|
||||
{
|
||||
@@ -120,6 +125,7 @@ public sealed class GalaxyDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadAsync surfaces rejected tags as bad status on the subscribe-once path.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_SubscribeOncePath_RejectedTagSurfacesAsBadStatus()
|
||||
{
|
||||
@@ -140,6 +146,7 @@ public sealed class GalaxyDriverReadTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that ReadAsync preserves reader status codes.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReadAsync_PreservesReaderStatusCodes()
|
||||
{
|
||||
|
||||
+13
-13
@@ -40,11 +40,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
/// <summary>Gets or sets a function to decide whether to accept a subscription.</summary>
|
||||
public Func<string, bool> Decide { get; set; } = _ => true;
|
||||
|
||||
/// <summary>Subscribes to bulk updates for the specified tag references.</summary>
|
||||
/// <param name="fullReferences">The tag references to subscribe to.</param>
|
||||
/// <param name="bufferedUpdateIntervalMs">The buffered update interval in milliseconds.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A list of subscription results.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<SubscribeResult>> SubscribeBulkAsync(
|
||||
IReadOnlyList<string> fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken)
|
||||
{
|
||||
@@ -77,19 +73,14 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
return Task.FromResult<IReadOnlyList<SubscribeResult>>(results);
|
||||
}
|
||||
|
||||
/// <summary>Unsubscribes from bulk updates for the specified item handles.</summary>
|
||||
/// <param name="itemHandles">The handles to unsubscribe.</param>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>A completed task.</returns>
|
||||
/// <inheritdoc />
|
||||
public Task UnsubscribeBulkAsync(IReadOnlyList<int> itemHandles, CancellationToken cancellationToken)
|
||||
{
|
||||
UnsubscribedHandles.AddRange(itemHandles);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>Streams events asynchronously.</summary>
|
||||
/// <param name="cancellationToken">Cancellation token for the operation.</param>
|
||||
/// <returns>An async enumerable of MX events.</returns>
|
||||
/// <inheritdoc />
|
||||
public IAsyncEnumerable<MxEvent> StreamEventsAsync(CancellationToken cancellationToken)
|
||||
=> _events.Reader.ReadAllAsync(cancellationToken);
|
||||
|
||||
@@ -113,6 +104,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies subscription allocates a handle and dispatches value changes.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_AllocatesHandle_AndDispatchesValueChange()
|
||||
{
|
||||
@@ -136,6 +128,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies two subscriptions for the same tag each receive updates.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_TwoSubscriptions_SameTag_FanOutOnePerSubscription()
|
||||
{
|
||||
@@ -180,6 +173,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies failed subscriptions do not dispatch events.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_FailedTag_DoesNotDispatchEvents()
|
||||
{
|
||||
@@ -201,6 +195,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies unsubscribe removes registration and calls gateway unsubscribe.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task UnsubscribeAsync_RemovesRegistration_AndCallsGwUnsubscribe()
|
||||
{
|
||||
@@ -224,6 +219,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies unsubscribing with an unknown handle is handled.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task UnsubscribeAsync_UnknownHandle_NoOp()
|
||||
{
|
||||
@@ -239,6 +235,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies subscription without a subscriber throws.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_NoSubscriber_Throws()
|
||||
{
|
||||
@@ -249,6 +246,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies subscription falls back to configured interval when zero is passed.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_FallsBackToConfiguredInterval_WhenCallerPassesZero()
|
||||
{
|
||||
@@ -269,6 +267,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies subscription respects caller's interval when non-zero.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_RespectsCallerInterval_WhenNonZero()
|
||||
{
|
||||
@@ -289,6 +288,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies subscription with empty tag list returns handle without calling gateway.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task SubscribeAsync_EmptyTagList_ReturnsHandleWithoutCallingGw()
|
||||
{
|
||||
@@ -304,7 +304,7 @@ public sealed class GalaxyDriverSubscribeTests
|
||||
/// <summary>A subscription handle from a foreign source.</summary>
|
||||
private sealed class ForeignHandle : ISubscriptionHandle
|
||||
{
|
||||
/// <summary>Gets the diagnostic identifier for this handle.</summary>
|
||||
/// <inheritdoc />
|
||||
public string DiagnosticId => "foreign-x";
|
||||
}
|
||||
|
||||
|
||||
+14
-22
@@ -26,8 +26,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
|
||||
private sealed class FakeHierarchySource(IReadOnlyList<GalaxyObject> objects) : IGalaxyHierarchySource
|
||||
{
|
||||
/// <summary>Returns the fake Galaxy object hierarchy.</summary>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<GalaxyObject>> GetHierarchyAsync(CancellationToken cancellationToken)
|
||||
=> Task.FromResult(objects);
|
||||
}
|
||||
@@ -37,36 +36,26 @@ public sealed class GalaxyDriverWriteTests
|
||||
/// <summary>Gets the list of variables added to this builder.</summary>
|
||||
public List<DriverAttributeInfo> Variables { get; } = [];
|
||||
|
||||
/// <summary>Adds 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) => this;
|
||||
/// <summary>Adds a variable to the variables list and returns a handle.</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 for the variable.</param>
|
||||
/// <inheritdoc />
|
||||
public IVariableHandle Variable(string browseName, string displayName, DriverAttributeInfo attributeInfo)
|
||||
{
|
||||
Variables.Add(attributeInfo);
|
||||
return new FakeHandle(attributeInfo.FullName);
|
||||
}
|
||||
/// <summary>No-op property adding operation for test compatibility.</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 value of the property.</param>
|
||||
/// <inheritdoc />
|
||||
public void AddProperty(string browseName, DriverDataType dataType, object? value) { }
|
||||
|
||||
private sealed class FakeHandle(string fullRef) : IVariableHandle
|
||||
{
|
||||
/// <summary>Gets the full reference for this variable handle.</summary>
|
||||
/// <inheritdoc />
|
||||
public string FullReference { get; } = fullRef;
|
||||
/// <summary>Marks this variable as an alarm condition and returns a noop sink.</summary>
|
||||
/// <param name="info">The alarm condition information.</param>
|
||||
/// <inheritdoc />
|
||||
public IAlarmConditionSink MarkAsAlarmCondition(AlarmConditionInfo info) => new NoopSink();
|
||||
/// <summary>No-op alarm transition handler.</summary>
|
||||
private sealed class NoopSink : IAlarmConditionSink {
|
||||
/// <summary>Handles alarm state transition events.</summary>
|
||||
/// <param name="args">The alarm event arguments.</param>
|
||||
/// <inheritdoc />
|
||||
public void OnTransition(AlarmEventArgs args) { }
|
||||
}
|
||||
}
|
||||
@@ -77,10 +66,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
/// <summary>Gets the list of write calls received by this writer.</summary>
|
||||
public List<(string FullRef, object? Value, SecurityClassification Resolved)> Calls { get; } = [];
|
||||
|
||||
/// <summary>Records write requests with their resolved security classifications.</summary>
|
||||
/// <param name="writes">The list of write requests to process.</param>
|
||||
/// <param name="securityResolver">Function to resolve security classification for each request.</param>
|
||||
/// <param name="cancellationToken">Token to cancel the operation.</param>
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<WriteResult>> WriteAsync(
|
||||
IReadOnlyList<WriteRequest> writes,
|
||||
Func<string, SecurityClassification> securityResolver,
|
||||
@@ -107,6 +93,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that WriteAsync routes through the injected writer and propagates values correctly.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_RoutesThroughInjectedWriter_AndPropagatesValues()
|
||||
{
|
||||
@@ -133,6 +120,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
/// <summary>Verifies that WriteAsync resolves every security classification from discovery data.</summary>
|
||||
/// <param name="mxSec">The raw MXAccess security integer from the discovery attribute.</param>
|
||||
/// <param name="expected">The expected resolved security classification.</param>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Theory]
|
||||
[InlineData(0, SecurityClassification.FreeAccess)]
|
||||
[InlineData(1, SecurityClassification.Operate)]
|
||||
@@ -157,6 +145,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that unknown tags resolve to FreeAccess classification and writes proceed.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_UnknownTag_ResolvesToFreeAccess_DefaultsToWrite()
|
||||
{
|
||||
@@ -172,6 +161,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that an empty write request returns empty without calling the writer.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_EmptyRequest_ReturnsEmpty_WithoutCallingWriter()
|
||||
{
|
||||
@@ -186,6 +176,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that WriteAsync throws when no writer is configured, referencing PR 4.4.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_NoWriter_Throws_PointingAtPR44()
|
||||
{
|
||||
@@ -197,6 +188,7 @@ public sealed class GalaxyDriverWriteTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that WriteAsync throws ObjectDisposedException after the driver is disposed.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WriteAsync_AfterDispose_Throws()
|
||||
{
|
||||
|
||||
@@ -31,6 +31,7 @@ public sealed class GalaxyTelemetryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that TracedGalaxySubscriber emits a subscribe_bulk span with tag count.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task TracedGalaxySubscriber_emits_subscribe_bulk_span_with_tag_count()
|
||||
{
|
||||
@@ -52,6 +53,7 @@ public sealed class GalaxyTelemetryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that TracedGalaxySubscriber records error and rethrows on failure.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task TracedGalaxySubscriber_records_error_and_rethrows_on_failure()
|
||||
{
|
||||
@@ -70,6 +72,7 @@ public sealed class GalaxyTelemetryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that TracedGalaxyDataWriter tags the secured write count.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task TracedGalaxyDataWriter_tags_secured_write_count()
|
||||
{
|
||||
@@ -106,6 +109,7 @@ public sealed class GalaxyTelemetryTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that TracedGalaxyHierarchySource tags the object count.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task TracedGalaxyHierarchySource_tags_object_count()
|
||||
{
|
||||
@@ -182,7 +186,9 @@ public sealed class GalaxyTelemetryTests
|
||||
|
||||
private sealed class FakeHierarchy : IGalaxyHierarchySource
|
||||
{
|
||||
/// <inheritdoc />
|
||||
/// <summary>Returns a fixed two-element hierarchy for telemetry testing.</summary>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
/// <returns>A task that resolves to a two-element Galaxy object list.</returns>
|
||||
public Task<IReadOnlyList<ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy.GalaxyObject>> GetHierarchyAsync(
|
||||
CancellationToken cancellationToken)
|
||||
=> Task.FromResult<IReadOnlyList<ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy.GalaxyObject>>(
|
||||
|
||||
+2
@@ -21,6 +21,8 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime;
|
||||
[Trait("Category", "Integration")]
|
||||
public sealed class GatewayGalaxyAlarmFeedLiveTests
|
||||
{
|
||||
/// <summary>Verifies that the live gateway delivers native alarm transitions through the consumer.</summary>
|
||||
/// <returns>A task that represents the asynchronous test operation.</returns>
|
||||
[Fact]
|
||||
public async Task Live_gateway_delivers_native_alarm_transitions_through_the_consumer()
|
||||
{
|
||||
|
||||
+3
@@ -18,6 +18,7 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Runtime;
|
||||
public sealed class GatewayGalaxyAlarmFeedTests
|
||||
{
|
||||
/// <summary>Verifies that the feed decodes active alarm snapshots and live transitions.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Decodes_active_alarm_snapshot_then_live_transition()
|
||||
{
|
||||
@@ -70,6 +71,7 @@ public sealed class GatewayGalaxyAlarmFeedTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the feed drops transitions with unspecified kind and empty messages.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Drops_transition_with_unspecified_kind_and_empty_message()
|
||||
{
|
||||
@@ -107,6 +109,7 @@ public sealed class GatewayGalaxyAlarmFeedTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the feed reopens the stream after a transport fault.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Reopens_stream_after_a_transport_fault()
|
||||
{
|
||||
|
||||
+7
@@ -28,6 +28,7 @@ public sealed class ReconnectSupervisorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reporting a transport failure drives the supervisor through reopen and replay cycles back to healthy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReportTransportFailure_DrivesThroughReopenReplay_BackToHealthy()
|
||||
{
|
||||
@@ -57,6 +58,7 @@ public sealed class ReconnectSupervisorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that reopen failures trigger retries and the supervisor stays in reopening state between attempts.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReopenFailure_RetriesUntilSuccess_StaysInReopeningBetweenAttempts()
|
||||
{
|
||||
@@ -75,6 +77,7 @@ public sealed class ReconnectSupervisorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that replay failures trigger a retry of the entire reopen-replay cycle.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task ReplayFailure_RetriesEntireCycle()
|
||||
{
|
||||
@@ -95,6 +98,7 @@ public sealed class ReconnectSupervisorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that repeated failure reports during recovery do not spawn parallel recovery loops.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task RepeatedFailureReports_DuringRecovery_DoNotSpawnParallelLoops()
|
||||
{
|
||||
@@ -122,6 +126,7 @@ public sealed class ReconnectSupervisorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that the last error reflects the most recent failure cause from recovery attempts.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task LastError_ReflectsMostRecentFailureCause()
|
||||
{
|
||||
@@ -141,6 +146,7 @@ public sealed class ReconnectSupervisorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that disposing the supervisor cancels a running recovery loop cleanly.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task Dispose_CancelsRunningRecoveryLoop_Cleanly()
|
||||
{
|
||||
@@ -170,6 +176,7 @@ public sealed class ReconnectSupervisorTests
|
||||
}
|
||||
|
||||
/// <summary>Verifies that waiting for healthy state returns immediately when already healthy.</summary>
|
||||
/// <returns>A task that represents the asynchronous operation.</returns>
|
||||
[Fact]
|
||||
public async Task WaitForHealthy_ReturnsImmediately_WhenAlreadyHealthy()
|
||||
{
|
||||
|
||||
+1
@@ -242,6 +242,7 @@ public sealed class SubscriptionRegistryTests
|
||||
public int TrackedItemHandleCount => _inner.TrackedItemHandleCount;
|
||||
|
||||
/// <summary>Gets the next subscription ID.</summary>
|
||||
/// <returns>A monotonically increasing subscription identifier.</returns>
|
||||
public long NextSubscriptionId() => _inner.NextSubscriptionId();
|
||||
|
||||
/// <summary>Registers a subscription with the given bindings.</summary>
|
||||
|
||||
Reference in New Issue
Block a user