fix(client-cli): resolve Low code-review findings (Client.CLI-002,003,004,006,007,008,009,010)
- Client.CLI-002: SubscribeCommand's neverWentBad list now requires the node to be present in lastStatus (i.e. received at least one update) so the 'suspect' bucket only contains observed nodes. - Client.CLI-003: every long-running command validates numeric option ranges (Interval / Depth / MaxDepth / Duration / Max) and throws CliFx CommandException on out-of-range values. - Client.CLI-004: SubscribeCommand carries XML summary docs on the type, ctor, every [CommandOption] property, and ExecuteAsync — matching the sibling commands' style. - Client.CLI-006: HistoryReadCommand parses --start / --end with InvariantCulture+UTC and surfaces FormatException as CommandException; every NodeIdParser.ParseRequired call wraps FormatException / ArgumentException as CommandException. - Client.CLI-007: CommandBase.ConfigureLogging calls Log.CloseAndFlush() before assigning a new Log.Logger so prior sinks are disposed. - Client.CLI-008: rewrote the subscribe and historyread sections of docs/Client.CLI.md (every flag documented, summary-bucket vocabulary, StandardDeviation aggregate, UTC --start/--end convention). - Client.CLI-009: SubscribeCommand / AlarmsCommand use named local handlers and detach them via -= after UnsubscribeAsync so no notification reaches the console after the command's output phase ends. - Client.CLI-010: added CommandRangeValidationTests, EventHandlerLifecycleTests, InputValidationErrorsTests, LoggerLifecycleTests, and SubscribeCommandSummaryTests pinning every Low fix; FakeOpcUaClientService gained AddDiscoveredVariable + RaiseDataChanged + BrowseResultsByParent helpers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -55,6 +55,14 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
new("ns=2;s=Node2", "Node2", "Variable", false)
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Optional per-parent-node browse results. When a key matches the requested parent's
|
||||
/// <see cref="NodeId.ToString" />, this dictionary takes precedence over <see cref="BrowseResults" />.
|
||||
/// Tests exercising recursive walks (Client.CLI-010) use it to model a real subtree whose
|
||||
/// child node ids do not collide on descent.
|
||||
/// </summary>
|
||||
public Dictionary<string, IReadOnlyList<BrowseResult>> BrowseResultsByParent { get; } = new();
|
||||
|
||||
public IReadOnlyList<DataValue> HistoryReadResult { get; set; } = new List<DataValue>
|
||||
{
|
||||
new(new Variant(10.0), StatusCodes.Good, DateTime.UtcNow.AddHours(-1), DateTime.UtcNow),
|
||||
@@ -68,6 +76,7 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
public Exception? ReadException { get; set; }
|
||||
public Exception? WriteException { get; set; }
|
||||
public Exception? ConditionRefreshException { get; set; }
|
||||
public Exception? SubscribeException { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool IsConnected => ConnectCalled && !DisconnectCalled;
|
||||
@@ -84,6 +93,12 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
/// <inheritdoc />
|
||||
public event EventHandler<ConnectionStateChangedEventArgs>? ConnectionStateChanged;
|
||||
|
||||
/// <summary>True when at least one handler is attached to <see cref="DataChanged" />.</summary>
|
||||
public bool HasDataChangedSubscribers => DataChanged != null;
|
||||
|
||||
/// <summary>True when at least one handler is attached to <see cref="AlarmEvent" />.</summary>
|
||||
public bool HasAlarmEventSubscribers => AlarmEvent != null;
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConnectionInfo> ConnectAsync(ConnectionSettings settings, CancellationToken ct = default)
|
||||
{
|
||||
@@ -120,6 +135,9 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
public Task<IReadOnlyList<BrowseResult>> BrowseAsync(NodeId? parentNodeId = null, CancellationToken ct = default)
|
||||
{
|
||||
BrowseNodeIds.Add(parentNodeId);
|
||||
var key = parentNodeId?.ToString();
|
||||
if (key != null && BrowseResultsByParent.TryGetValue(key, out var keyed))
|
||||
return Task.FromResult(keyed);
|
||||
return Task.FromResult(BrowseResults);
|
||||
}
|
||||
|
||||
@@ -127,6 +145,7 @@ public sealed class FakeOpcUaClientService : IOpcUaClientService
|
||||
public Task SubscribeAsync(NodeId nodeId, int intervalMs = 1000, CancellationToken ct = default)
|
||||
{
|
||||
SubscribeCalls.Add((nodeId, intervalMs));
|
||||
if (SubscribeException != null) throw SubscribeException;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user