feat(focas): resolve equipment-tag refs (read + write) via EquipmentTagRefResolver

This commit is contained in:
Joseph Doherty
2026-06-13 11:24:45 -04:00
parent 5ebf541f54
commit 34a42486dc
3 changed files with 126 additions and 2 deletions
@@ -0,0 +1,67 @@
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.FOCAS;
namespace ZB.MOM.WW.OtOpcUa.Driver.FOCAS.Tests;
[Trait("Category", "Unit")]
public class FocasEquipmentTagTests
{
[Fact]
public void Parses_equipment_tagconfig_into_a_transient_definition()
{
var json = """{"deviceHostAddress":"focas://10.0.0.5:8193","address":"R100","dataType":"Byte"}""";
FocasEquipmentTagParser.TryParse(json, out var def).ShouldBeTrue();
def!.Name.ShouldBe(json);
def.DeviceHostAddress.ShouldBe("focas://10.0.0.5:8193");
def.Address.ShouldBe("R100");
def.DataType.ShouldBe(FocasDataType.Byte);
}
[Fact]
public void Rejects_a_non_address_blob()
=> FocasEquipmentTagParser.TryParse("""{"FullName":"x"}""", out _).ShouldBeFalse();
[Fact]
public void Rejects_garbage()
=> FocasEquipmentTagParser.TryParse("not json", out _).ShouldBeFalse();
[Fact]
public void Rejects_address_as_a_json_number()
=> FocasEquipmentTagParser.TryParse(
"""{"address":100,"dataType":"Int32"}""", out _).ShouldBeFalse();
[Fact]
public void Rejects_a_blank_address()
=> FocasEquipmentTagParser.TryParse(
"""{"address":" ","dataType":"Int32"}""", out _).ShouldBeFalse();
// FOCAS's only mandatory address field is the canonical Address STRING (no narrow numeric
// TagConfig field like Modbus's ushort register address), so the Modbus "numeric out-of-range"
// rejection case has no analog here — covered instead by the json-number + blank-address cases.
/// <summary>
/// End-to-end driver-level proof: a FOCAS driver with NO authored tags can still read an
/// equipment-tag ref (the raw TagConfig JSON) — the resolver parses it into a transient
/// definition and the read goes to the wire instead of returning BadNodeIdUnknown.
/// </summary>
[Fact]
public async Task Driver_resolves_an_equipment_ref_and_reads_instead_of_BadNodeIdUnknown()
{
var json = """{"deviceHostAddress":"focas://10.0.0.5:8193","address":"R100","dataType":"Byte"}""";
var factory = new FakeFocasClientFactory { Customise = () => new FakeFocasClient { Values = { ["R100"] = (sbyte)42 } } };
var drv = new FocasDriver(new FocasDriverOptions
{
Devices = [new FocasDeviceOptions("focas://10.0.0.5:8193")],
Tags = [],
Probe = new FocasProbeOptions { Enabled = false },
}, "focas-eq", factory);
await drv.InitializeAsync("{}", CancellationToken.None);
var r = await drv.ReadAsync([json], CancellationToken.None);
r[0].StatusCode.ShouldBe(FocasStatusMapper.Good);
r[0].StatusCode.ShouldNotBe(FocasStatusMapper.BadNodeIdUnknown);
r[0].Value.ShouldBe((sbyte)42);
}
}