c0379742bc
Apply the reviewed Modbus exemplar to the AbCip multi-device driver:
- Mapper AbCipEquipmentTagParser -> AbCipTagDefinitionFactory; TryParse -> FromTagConfig(tagConfig, rawPath, out def); drop leading-{ heuristic + the deviceHostAddress key; add inverse ToTagConfig; round-trip arrays/members/safety for coverage.
- Options: Tags (typed) -> RawTags (RawTagEntry list); Devices list kept.
- Driver: byName-only resolver over _tagsByRawPath (Ordinal); table built from RawTags via FromTagConfig, threading entry.DeviceName (device routing key) + entry.WriteIdempotent onto the def. Multi-device routing keys on DeviceName-or-host via a _devicesByRoutingKey index; discovery groups _declaredTags by RoutesToDevice.
- Factory: retire the pre-declared Tags DTO/BuildTag; bind List<RawTagEntry> RawTags.
- Probe: derive probe tag path from RawTags via the mapper.
- Cli BuildOptions projects typed tags -> RawTagEntry (ToTagConfig).
- Tests: AbCipRawTags helper (typed def -> RawTagEntry); parser tests -> FromTagConfig; blob-fallback driver tests rewritten to author via RawTags + read by RawPath. Driver.AbCip.Tests 342/342, Cli.Tests 42/42.
TODO(v3 WaveC): DeviceName becomes a pure logical name once the device connection host moves to the Device row's DeviceConfig.
51 lines
1.9 KiB
C#
51 lines
1.9 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.OtOpcUa.Core.Abstractions;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.AbCip;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests;
|
|
|
|
/// <summary>
|
|
/// CONV-4 — <see cref="AbCipDriver.ResolveHost"/> keys per-host resilience (bulkhead / circuit
|
|
/// breaker). It must resolve an authored tag (by its RawPath) to its OWN device host, not fall back
|
|
/// to the first device — otherwise a broken device B trips device A's breaker. Unknown refs keep the
|
|
/// current first-device fallback.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class AbCipResolveHostTests
|
|
{
|
|
private const string DeviceA = "ab://10.0.0.5/1,0";
|
|
private const string DeviceB = "ab://10.0.0.6/1,0";
|
|
|
|
private static AbCipDriver NewDriver(params RawTagEntry[] rawTags) => new(
|
|
new AbCipDriverOptions
|
|
{
|
|
Devices = [new AbCipDeviceOptions(DeviceA), new AbCipDeviceOptions(DeviceB)],
|
|
RawTags = rawTags,
|
|
Probe = new AbCipProbeOptions { Enabled = false },
|
|
},
|
|
"abcip-1", new FakeAbCipTagFactory());
|
|
|
|
/// <summary>An authored tag routed to device B resolves to device B's host, not the first device.</summary>
|
|
[Fact]
|
|
public async Task ResolveHost_TagRoutedToDeviceB_ReturnsItsOwnDeviceHost()
|
|
{
|
|
var drv = NewDriver(new RawTagEntry(
|
|
RawPath: "line1/motorB/speed",
|
|
TagConfig: """{"tagPath":"Motor1.Speed","dataType":"DInt"}""",
|
|
WriteIdempotent: false,
|
|
DeviceName: DeviceB));
|
|
await drv.InitializeAsync("{}", CancellationToken.None);
|
|
|
|
drv.ResolveHost("line1/motorB/speed").ShouldBe(DeviceB);
|
|
}
|
|
|
|
/// <summary>An unresolvable ref keeps the current first-device fallback.</summary>
|
|
[Fact]
|
|
public void ResolveHost_UnknownRef_KeepsCurrentFallback()
|
|
{
|
|
var drv = NewDriver();
|
|
drv.ResolveHost("totally-unknown").ShouldBe(DeviceA);
|
|
}
|
|
}
|