330 lines
12 KiB
C#
330 lines
12 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.AbCip.PlcFamilies;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
|
|
|
/// <summary>
|
|
/// PR abcip-3.1 — coverage for the per-device CIP <c>ConnectionSize</c> override.
|
|
/// Asserts (a) the value flows from <see cref="AbCipDeviceOptions"/> into every
|
|
/// <see cref="AbCipTagCreateParams"/> the driver builds, (b) the family default kicks in
|
|
/// when the override is unset, (c) values outside the Kepware-supported range are rejected
|
|
/// at <c>InitializeAsync</c>, and (d) the legacy-firmware warning fires when a CompactLogix
|
|
/// narrow-cap device is configured above 511 bytes.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class AbCipConnectionSizeTests
|
|
{
|
|
// ---- options threading ----
|
|
|
|
[Fact]
|
|
public async Task Custom_ConnectionSize_flows_from_device_options_into_create_params()
|
|
{
|
|
var factory = new FakeAbCipTagFactory();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions(
|
|
HostAddress: "ab://10.0.0.5/1,0",
|
|
PlcFamily: AbCipPlcFamily.ControlLogix,
|
|
ConnectionSize: 1500),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
Tags =
|
|
[
|
|
new AbCipTagDefinition(
|
|
Name: "Speed",
|
|
DeviceHostAddress: "ab://10.0.0.5/1,0",
|
|
TagPath: "Speed",
|
|
DataType: AbCipDataType.DInt),
|
|
],
|
|
}, "drv-1", tagFactory: factory);
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
await drv.ReadAsync(["Speed"], CancellationToken.None);
|
|
|
|
factory.Tags["Speed"].CreationParams.ConnectionSize.ShouldBe(1500);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unset_ConnectionSize_falls_back_to_ControlLogix_family_default()
|
|
{
|
|
var factory = new FakeAbCipTagFactory();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0", AbCipPlcFamily.ControlLogix)],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
Tags =
|
|
[
|
|
new AbCipTagDefinition("Speed", "ab://10.0.0.5/1,0", "Speed", AbCipDataType.DInt),
|
|
],
|
|
}, "drv-1", tagFactory: factory);
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
await drv.ReadAsync(["Speed"], CancellationToken.None);
|
|
|
|
factory.Tags["Speed"].CreationParams.ConnectionSize
|
|
.ShouldBe(AbCipPlcFamilyProfile.ControlLogix.DefaultConnectionSize);
|
|
factory.Tags["Speed"].CreationParams.ConnectionSize.ShouldBe(4002);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unset_ConnectionSize_falls_back_to_CompactLogix_family_default()
|
|
{
|
|
var factory = new FakeAbCipTagFactory();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0", AbCipPlcFamily.CompactLogix)],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
Tags =
|
|
[
|
|
new AbCipTagDefinition("Speed", "ab://10.0.0.5/1,0", "Speed", AbCipDataType.DInt),
|
|
],
|
|
}, "drv-1", tagFactory: factory);
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
await drv.ReadAsync(["Speed"], CancellationToken.None);
|
|
|
|
factory.Tags["Speed"].CreationParams.ConnectionSize.ShouldBe(504);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Unset_ConnectionSize_falls_back_to_Micro800_family_default()
|
|
{
|
|
var factory = new FakeAbCipTagFactory();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices = [new AbCipDeviceOptions("ab://10.0.0.6/", AbCipPlcFamily.Micro800)],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
Tags =
|
|
[
|
|
new AbCipTagDefinition("Speed", "ab://10.0.0.6/", "Speed", AbCipDataType.DInt),
|
|
],
|
|
}, "drv-1", tagFactory: factory);
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
await drv.ReadAsync(["Speed"], CancellationToken.None);
|
|
|
|
factory.Tags["Speed"].CreationParams.ConnectionSize.ShouldBe(488);
|
|
}
|
|
|
|
// ---- range validation ----
|
|
|
|
[Theory]
|
|
[InlineData(499)]
|
|
[InlineData(0)]
|
|
[InlineData(-1)]
|
|
[InlineData(4003)]
|
|
[InlineData(10000)]
|
|
public async Task Out_of_range_ConnectionSize_throws_at_InitializeAsync(int badSize)
|
|
{
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions(
|
|
HostAddress: "ab://10.0.0.5/1,0",
|
|
PlcFamily: AbCipPlcFamily.ControlLogix,
|
|
ConnectionSize: badSize),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
}, "drv-1");
|
|
|
|
var ex = await Should.ThrowAsync<InvalidOperationException>(
|
|
() => drv.InitializeAsync("{}", CancellationToken.None));
|
|
ex.Message.ShouldContain("ConnectionSize");
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(500)]
|
|
[InlineData(504)]
|
|
[InlineData(2000)]
|
|
[InlineData(4002)]
|
|
public async Task In_range_ConnectionSize_initialises_cleanly(int goodSize)
|
|
{
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions(
|
|
HostAddress: "ab://10.0.0.5/1,0",
|
|
PlcFamily: AbCipPlcFamily.ControlLogix,
|
|
ConnectionSize: goodSize),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
}, "drv-1");
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
drv.GetDeviceState("ab://10.0.0.5/1,0")!.ConnectionSize.ShouldBe(goodSize);
|
|
}
|
|
|
|
// ---- legacy-firmware warning ----
|
|
|
|
[Fact]
|
|
public async Task Oversized_ConnectionSize_on_CompactLogix_emits_legacy_warning()
|
|
{
|
|
var warnings = new List<string>();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions(
|
|
HostAddress: "ab://10.0.0.5/1,0",
|
|
PlcFamily: AbCipPlcFamily.CompactLogix,
|
|
ConnectionSize: 1500),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
OnWarning = warnings.Add,
|
|
}, "drv-1");
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
|
|
warnings.ShouldHaveSingleItem();
|
|
warnings[0].ShouldContain("CompactLogix");
|
|
warnings[0].ShouldContain("1500");
|
|
warnings[0].ShouldContain("Forward Open");
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Within_legacy_cap_on_CompactLogix_does_not_warn()
|
|
{
|
|
var warnings = new List<string>();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions(
|
|
HostAddress: "ab://10.0.0.5/1,0",
|
|
PlcFamily: AbCipPlcFamily.CompactLogix,
|
|
ConnectionSize: 504),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
OnWarning = warnings.Add,
|
|
}, "drv-1");
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
|
|
warnings.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Oversized_ConnectionSize_on_ControlLogix_does_not_warn()
|
|
{
|
|
// ControlLogix profile default is 4002 (Large Forward Open) — the warning is only
|
|
// meaningful when the family default is in the legacy-cap bucket. FW20+ ControlLogix
|
|
// happily accepts 1500-byte connections, so no warning fires.
|
|
var warnings = new List<string>();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions(
|
|
HostAddress: "ab://10.0.0.5/1,0",
|
|
PlcFamily: AbCipPlcFamily.ControlLogix,
|
|
ConnectionSize: 1500),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
OnWarning = warnings.Add,
|
|
}, "drv-1");
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
|
|
warnings.ShouldBeEmpty();
|
|
}
|
|
|
|
[Fact]
|
|
public async Task Oversized_ConnectionSize_on_Micro800_emits_legacy_warning()
|
|
{
|
|
// Micro800 default is 488 (well under the legacy cap), so any over-511 override
|
|
// triggers the same family-mismatch warning.
|
|
var warnings = new List<string>();
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions(
|
|
HostAddress: "ab://10.0.0.6/",
|
|
PlcFamily: AbCipPlcFamily.Micro800,
|
|
ConnectionSize: 1000),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
OnWarning = warnings.Add,
|
|
}, "drv-1");
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
|
|
warnings.ShouldHaveSingleItem();
|
|
warnings[0].ShouldContain("Micro800");
|
|
}
|
|
|
|
// ---- DeviceState resolved ConnectionSize ----
|
|
|
|
[Fact]
|
|
public async Task DeviceState_ConnectionSize_reflects_override_when_set()
|
|
{
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices =
|
|
[
|
|
new AbCipDeviceOptions("ab://10.0.0.5/1,0", AbCipPlcFamily.ControlLogix, ConnectionSize: 2000),
|
|
],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
}, "drv-1");
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
drv.GetDeviceState("ab://10.0.0.5/1,0")!.ConnectionSize.ShouldBe(2000);
|
|
}
|
|
|
|
[Fact]
|
|
public async Task DeviceState_ConnectionSize_reflects_family_default_when_unset()
|
|
{
|
|
var drv = new AbCipDriver(new AbCipDriverOptions
|
|
{
|
|
Devices = [new AbCipDeviceOptions("ab://10.0.0.5/1,0", AbCipPlcFamily.CompactLogix)],
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
}, "drv-1");
|
|
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
drv.GetDeviceState("ab://10.0.0.5/1,0")!.ConnectionSize.ShouldBe(504);
|
|
}
|
|
|
|
// ---- AbCipConnectionSize constants ----
|
|
|
|
[Fact]
|
|
public void Constants_match_documented_Kepware_range()
|
|
{
|
|
AbCipConnectionSize.Min.ShouldBe(500);
|
|
AbCipConnectionSize.Max.ShouldBe(4002);
|
|
AbCipConnectionSize.LegacyFirmwareCap.ShouldBe(511);
|
|
}
|
|
|
|
// ---- DriverConfig DTO path (DriverFactoryRegistry-bound deployments) ----
|
|
|
|
[Fact]
|
|
public async Task Driver_factory_threads_ConnectionSize_through_config_json()
|
|
{
|
|
// The bootstrapper-driven path deserialises driver config from JSON in the central
|
|
// DB (sp_PublishGeneration → DriverInstance.DriverConfig). The DTO must surface
|
|
// ConnectionSize so production deployments don't lose the override at the wire.
|
|
var json = """
|
|
{
|
|
"Devices": [
|
|
{
|
|
"HostAddress": "ab://10.0.0.5/1,0",
|
|
"PlcFamily": "ControlLogix",
|
|
"ConnectionSize": 1500
|
|
}
|
|
],
|
|
"Probe": { "Enabled": false }
|
|
}
|
|
""";
|
|
var drv = AbCipDriverFactoryExtensions.CreateInstance("drv-1", json);
|
|
// CreateInstance returns a fully-built driver; we kick InitializeAsync to surface the
|
|
// resolved DeviceState.ConnectionSize.
|
|
await drv.InitializeAsync(json, CancellationToken.None);
|
|
drv.GetDeviceState("ab://10.0.0.5/1,0")!.ConnectionSize.ShouldBe(1500);
|
|
}
|
|
}
|