Files
lmxopcua/tests/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli.Tests/WriteCommandRegionValidationTests.cs
Joseph Doherty 80ef8806e0 fix(driver-modbus-cli): resolve Low code-review findings (Driver.Modbus.Cli-003,004,005,006,007,008)
- Driver.Modbus.Cli-003: ModbusCommandBase.ValidateEndpoint rejects
  --port outside 1..65535, non-positive --timeout-ms, and --unit-id
  outside 1..247.
- Driver.Modbus.Cli-004: wrapped SubscribeCommand's OnDataChange handler
  body in a try/catch (warn-and-swallow) and serialised the console
  write through a lock.
- Driver.Modbus.Cli-005: Probe / Read / Write now catch the
  cancellation-during-init OperationCanceledException and print
  'Cancelled.' instead of dumping a stack trace.
- Driver.Modbus.Cli-006: ProbeCommand.ComputeVerdict derives the headline
  from BOTH the driver state and the probe snapshot's OPC UA quality
  class so the headline can't disagree with the wire result.
- Driver.Modbus.Cli-007: docs/Driver.Modbus.Cli.md carries an explicit
  'CLI scope' callout — the address-string grammar is a DriverConfig
  JSON feature; the CLI takes the structured triple only.
- Driver.Modbus.Cli-008: pinned BuildOptions, ValidateEndpoint, the
  region-validation guards, ComputeVerdict, and the cancellation-during-
  initialize paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 08:35:05 -04:00

64 lines
2.3 KiB
C#

using CliFx.Infrastructure;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli.Commands;
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli.Tests;
/// <summary>
/// Covers the branch validation inside <see cref="WriteCommand.ExecuteAsync"/>:
/// 1. Driver.Modbus.Cli-002 — write to <see cref="ModbusRegion.Coils"/> must use
/// <c>--type Bool</c>.
/// 2. Read-only regions (DiscreteInputs / InputRegisters) reject any write.
/// The actual driver call is never reached for these guard cases — they throw a
/// <see cref="CliFx.Exceptions.CommandException"/> before the driver is constructed,
/// so we can exercise <c>ExecuteAsync</c> against an unreachable host.
/// </summary>
[Trait("Category", "Unit")]
public sealed class WriteCommandRegionValidationTests
{
[Theory]
[InlineData(ModbusRegion.DiscreteInputs, ModbusDataType.Bool, "0")]
[InlineData(ModbusRegion.InputRegisters, ModbusDataType.UInt16, "1")]
public async Task ExecuteAsync_rejects_read_only_regions(
ModbusRegion region, ModbusDataType type, string value)
{
var sut = new WriteCommand
{
// Host is required, but the guard fires before any socket use.
Host = "127.0.0.1",
Region = region,
Address = 0,
DataType = type,
Value = value,
};
using var console = new FakeInMemoryConsole();
await Should.ThrowAsync<CliFx.Exceptions.CommandException>(
async () => await sut.ExecuteAsync(console));
}
[Theory]
[InlineData(ModbusDataType.UInt16)]
[InlineData(ModbusDataType.Int16)]
[InlineData(ModbusDataType.Float32)]
[InlineData(ModbusDataType.Int32)]
public async Task ExecuteAsync_rejects_non_Bool_type_for_Coils_region(ModbusDataType type)
{
var sut = new WriteCommand
{
Host = "127.0.0.1",
Region = ModbusRegion.Coils,
Address = 5,
DataType = type,
Value = "42",
};
using var console = new FakeInMemoryConsole();
var ex = await Should.ThrowAsync<CliFx.Exceptions.CommandException>(
async () => await sut.ExecuteAsync(console));
ex.Message.ShouldContain("Coils");
ex.Message.ShouldContain("Bool");
}
}