Files
lmxopcua/tests/Client/ZB.MOM.WW.OtOpcUa.Client.CLI.Tests/EventHandlerLifecycleTests.cs
Joseph Doherty 7fe9f16cf8 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>
2026-05-23 11:13:08 -04:00

60 lines
2.0 KiB
C#

using Opc.Ua;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Commands;
using ZB.MOM.WW.OtOpcUa.Client.CLI.Tests.Fakes;
using ZB.MOM.WW.OtOpcUa.Client.Shared.Models;
namespace ZB.MOM.WW.OtOpcUa.Client.CLI.Tests;
/// <summary>
/// Regression tests for Client.CLI-009: long-running commands must detach their event handlers
/// from the service before the command finishes, so notifications fired during teardown don't
/// land in a disposed console.
/// </summary>
public class EventHandlerLifecycleTests
{
[Fact]
public async Task SubscribeCommand_AfterExit_DataChangedEventHasNoSubscribers()
{
var fakeService = new FakeOpcUaClientService();
var factory = new FakeOpcUaClientServiceFactory(fakeService);
var command = new SubscribeCommand(factory)
{
Url = "opc.tcp://localhost:4840",
NodeId = "ns=2;s=Node",
DurationSeconds = 1
};
using var console = TestConsoleHelper.CreateConsole();
await command.ExecuteAsync(console);
// The fake's event should have no subscribers after the command completes — every
// handler attached by the command must have been detached during teardown.
fakeService.HasDataChangedSubscribers.ShouldBeFalse(
"SubscribeCommand must detach its DataChanged handler before returning.");
}
[Fact]
public async Task AlarmsCommand_AfterExit_AlarmEventHasNoSubscribers()
{
var fakeService = new FakeOpcUaClientService();
var factory = new FakeOpcUaClientServiceFactory(fakeService);
var command = new AlarmsCommand(factory)
{
Url = "opc.tcp://localhost:4840"
};
using var console = TestConsoleHelper.CreateConsole();
var task = Task.Run(async () => await command.ExecuteAsync(console));
await Task.Delay(150);
console.RequestCancellation();
await task;
fakeService.HasAlarmEventSubscribers.ShouldBeFalse(
"AlarmsCommand must detach its AlarmEvent handler before returning.");
}
}