Files
lmxopcua/tests/Server/ZB.MOM.WW.OtOpcUa.Admin.Tests/ModbusOptionsViewModelTests.cs
Joseph Doherty a25593a9c6 chore: organize solution into module folders (Core/Server/Drivers/Client/Tooling)
Group all 69 projects into category subfolders under src/ and tests/ so the
Rider Solution Explorer mirrors the module structure. Folders: Core, Server,
Drivers (with a nested Driver CLIs subfolder), Client, Tooling.

- Move every project folder on disk with git mv (history preserved as renames).
- Recompute relative paths in 57 .csproj files: cross-category ProjectReferences,
  the lib/ HintPath+None refs in Driver.Historian.Wonderware, and the external
  mxaccessgw refs in Driver.Galaxy and its test project.
- Rebuild ZB.MOM.WW.OtOpcUa.slnx with nested solution folders.
- Re-prefix project paths in functional scripts (e2e, compliance, smoke SQL,
  integration, install).

Build green (0 errors); unit tests pass. Docs left for a separate pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 01:55:28 -04:00

137 lines
6.1 KiB
C#

using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Admin.Components.Pages.Modbus;
using ZB.MOM.WW.OtOpcUa.Driver.Modbus;
namespace ZB.MOM.WW.OtOpcUa.Admin.Tests;
/// <summary>
/// #145 Admin UI: smoke coverage for the ModbusOptionsEditor view model. The Blazor
/// component itself is exercised in browser-runtime tests; this fixture pins the default
/// values the form initialises to so a regression that flips an unedited row to a
/// non-default value gets caught.
/// </summary>
[Trait("Category", "Unit")]
public sealed class ModbusOptionsViewModelTests
{
[Fact]
public void DriversTab_Serialized_Defaults_RoundTrip_Through_Factory()
{
// #147 — the form's SaveAsync serialises ModbusOptionsViewModel to the JSON DTO
// shape ModbusDriverFactoryExtensions consumes. This test pins the round-trip:
// unedited form → JSON → driver instance → options match defaults.
var vm = new ModbusOptionsEditor.ModbusOptionsViewModel();
var json = SerializeForRoundTrip(vm);
var driver = ModbusDriverFactoryExtensions.CreateInstance("modbus-roundtrip", json);
var opts = (ModbusDriverOptions)typeof(ModbusDriver)
.GetField("_options", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
.GetValue(driver)!;
opts.Host.ShouldBe(vm.Host);
opts.Port.ShouldBe(vm.Port);
opts.UnitId.ShouldBe(vm.UnitId);
opts.Family.ShouldBe(vm.Family);
opts.MelsecSubFamily.ShouldBe(vm.MelsecSubFamily);
opts.KeepAlive.Enabled.ShouldBe(vm.KeepAliveEnabled);
opts.MaxRegistersPerRead.ShouldBe((ushort)vm.MaxRegistersPerRead);
opts.MaxCoilsPerRead.ShouldBe((ushort)vm.MaxCoilsPerRead);
opts.MaxReadGap.ShouldBe((ushort)vm.MaxReadGap);
opts.WriteOnChangeOnly.ShouldBe(vm.WriteOnChangeOnly);
}
[Fact]
public void DriversTab_Serializes_Edited_Values_Correctly()
{
// Sanity: changing a few fields in the view model produces a JSON the factory
// accepts and the resulting driver carries the edited values.
var vm = new ModbusOptionsEditor.ModbusOptionsViewModel
{
Host = "10.5.5.5",
Port = 1502,
UnitId = 7,
Family = ModbusFamily.DL205,
MaxReadGap = 12,
WriteOnChangeOnly = true,
};
var json = SerializeForRoundTrip(vm);
var driver = ModbusDriverFactoryExtensions.CreateInstance("modbus-edited", json);
var opts = (ModbusDriverOptions)typeof(ModbusDriver)
.GetField("_options", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!
.GetValue(driver)!;
opts.Host.ShouldBe("10.5.5.5");
opts.Port.ShouldBe(1502);
opts.UnitId.ShouldBe((byte)7);
opts.Family.ShouldBe(ModbusFamily.DL205);
opts.MaxReadGap.ShouldBe((ushort)12);
opts.WriteOnChangeOnly.ShouldBeTrue();
}
/// <summary>
/// Mirror of DriversTab.razor's SerializeModbusOptions — kept here so the test
/// doesn't have to reach through Blazor component plumbing to invoke it. If the
/// component method signature drifts, update both.
/// </summary>
private static string SerializeForRoundTrip(ModbusOptionsEditor.ModbusOptionsViewModel m) =>
System.Text.Json.JsonSerializer.Serialize(new
{
host = m.Host,
port = m.Port,
unitId = m.UnitId,
family = m.Family.ToString(),
melsecSubFamily = m.MelsecSubFamily.ToString(),
keepAlive = new
{
enabled = m.KeepAliveEnabled,
timeMs = m.KeepAliveTimeSec * 1000,
intervalMs = m.KeepAliveIntervalSec * 1000,
retryCount = m.KeepAliveRetryCount,
},
reconnect = new
{
initialDelayMs = m.ReconnectInitialDelayMs,
maxDelayMs = m.ReconnectMaxDelayMs,
backoffMultiplier = m.ReconnectBackoffMultiplier,
},
maxRegistersPerRead = m.MaxRegistersPerRead,
maxRegistersPerWrite = m.MaxRegistersPerWrite,
maxCoilsPerRead = m.MaxCoilsPerRead,
maxReadGap = m.MaxReadGap,
useFC15ForSingleCoilWrites = m.UseFC15ForSingleCoilWrites,
useFC16ForSingleRegisterWrites = m.UseFC16ForSingleRegisterWrites,
writeOnChangeOnly = m.WriteOnChangeOnly,
tags = Array.Empty<object>(),
});
[Fact]
public void Defaults_Match_DriverOption_Defaults()
{
var vm = new ModbusOptionsEditor.ModbusOptionsViewModel();
var driverDefault = new ModbusDriverOptions();
vm.Host.ShouldBe(driverDefault.Host);
vm.Port.ShouldBe(driverDefault.Port);
vm.UnitId.ShouldBe(driverDefault.UnitId);
vm.Family.ShouldBe(driverDefault.Family);
vm.MelsecSubFamily.ShouldBe(driverDefault.MelsecSubFamily);
vm.KeepAliveEnabled.ShouldBe(driverDefault.KeepAlive.Enabled);
vm.KeepAliveTimeSec.ShouldBe((int)driverDefault.KeepAlive.Time.TotalSeconds);
vm.KeepAliveIntervalSec.ShouldBe((int)driverDefault.KeepAlive.Interval.TotalSeconds);
vm.KeepAliveRetryCount.ShouldBe(driverDefault.KeepAlive.RetryCount);
vm.ReconnectInitialDelayMs.ShouldBe((int)driverDefault.Reconnect.InitialDelay.TotalMilliseconds);
vm.ReconnectMaxDelayMs.ShouldBe((int)driverDefault.Reconnect.MaxDelay.TotalMilliseconds);
vm.ReconnectBackoffMultiplier.ShouldBe(driverDefault.Reconnect.BackoffMultiplier);
vm.MaxRegistersPerRead.ShouldBe(driverDefault.MaxRegistersPerRead);
vm.MaxRegistersPerWrite.ShouldBe(driverDefault.MaxRegistersPerWrite);
vm.MaxCoilsPerRead.ShouldBe(driverDefault.MaxCoilsPerRead);
vm.MaxReadGap.ShouldBe(driverDefault.MaxReadGap);
vm.UseFC15ForSingleCoilWrites.ShouldBe(driverDefault.UseFC15ForSingleCoilWrites);
vm.UseFC16ForSingleRegisterWrites.ShouldBe(driverDefault.UseFC16ForSingleRegisterWrites);
vm.WriteOnChangeOnly.ShouldBe(driverDefault.WriteOnChangeOnly);
}
}