diff --git a/infra/lmxfakeproxy/TagMapper.cs b/infra/lmxfakeproxy/TagMapper.cs new file mode 100644 index 0000000..a41abf9 --- /dev/null +++ b/infra/lmxfakeproxy/TagMapper.cs @@ -0,0 +1,43 @@ +using LmxFakeProxy.Grpc; + +namespace LmxFakeProxy; + +public class TagMapper +{ + private readonly string _prefix; + + public TagMapper(string prefix) + { + _prefix = prefix; + } + + public string ToOpcNodeId(string lmxTag) => $"{_prefix}{lmxTag}"; + + public static object ParseWriteValue(string value) + { + if (double.TryParse(value, System.Globalization.NumberStyles.Float, + System.Globalization.CultureInfo.InvariantCulture, out var d)) + return d; + if (bool.TryParse(value, out var b)) + return b; + return value; + } + + public static string MapQuality(uint statusCode) + { + if (statusCode == 0) return "Good"; + if ((statusCode & 0x80000000) != 0) return "Bad"; + return "Uncertain"; + } + + public static VtqMessage ToVtqMessage(string tag, object? value, DateTime timestampUtc, uint statusCode) + { + return new VtqMessage + { + Tag = tag, + Value = value?.ToString() ?? string.Empty, + TimestampUtcTicks = timestampUtc.Ticks, + Quality = MapQuality(statusCode) + }; + } +} diff --git a/infra/lmxfakeproxy/tests/LmxFakeProxy.Tests/TagMappingTests.cs b/infra/lmxfakeproxy/tests/LmxFakeProxy.Tests/TagMappingTests.cs new file mode 100644 index 0000000..69cf2e6 --- /dev/null +++ b/infra/lmxfakeproxy/tests/LmxFakeProxy.Tests/TagMappingTests.cs @@ -0,0 +1,84 @@ +using Xunit; + +namespace LmxFakeProxy.Tests; + +public class TagMappingTests +{ + [Fact] + public void ToOpcNodeId_PrependsPrefix() + { + var mapper = new TagMapper("ns=3;s="); + Assert.Equal("ns=3;s=Motor.Speed", mapper.ToOpcNodeId("Motor.Speed")); + } + + [Fact] + public void ToOpcNodeId_CustomPrefix() + { + var mapper = new TagMapper("ns=2;s=MyFolder."); + Assert.Equal("ns=2;s=MyFolder.Pump.Pressure", mapper.ToOpcNodeId("Pump.Pressure")); + } + + [Fact] + public void ToOpcNodeId_EmptyPrefix_PassesThrough() + { + var mapper = new TagMapper(""); + Assert.Equal("Motor.Speed", mapper.ToOpcNodeId("Motor.Speed")); + } + + [Fact] + public void ParseWriteValue_Double() + { + Assert.Equal(42.5, TagMapper.ParseWriteValue("42.5")); + Assert.IsType(TagMapper.ParseWriteValue("42.5")); + } + + [Fact] + public void ParseWriteValue_Bool() + { + Assert.Equal(true, TagMapper.ParseWriteValue("true")); + Assert.Equal(false, TagMapper.ParseWriteValue("False")); + } + + [Fact] + public void ParseWriteValue_Uint() + { + // "100" parses as double first (double.TryParse succeeds for integers) + var result = TagMapper.ParseWriteValue("100"); + Assert.IsType(result); + } + + [Fact] + public void ParseWriteValue_FallsBackToString() + { + Assert.Equal("hello", TagMapper.ParseWriteValue("hello")); + Assert.IsType(TagMapper.ParseWriteValue("hello")); + } + + [Fact] + public void MapStatusCode_Good() + { + Assert.Equal("Good", TagMapper.MapQuality(0)); + } + + [Fact] + public void MapStatusCode_Bad() + { + Assert.Equal("Bad", TagMapper.MapQuality(0x80000000)); + } + + [Fact] + public void MapStatusCode_Uncertain() + { + Assert.Equal("Uncertain", TagMapper.MapQuality(0x40000000)); + } + + [Fact] + public void ToVtqMessage_ConvertsCorrectly() + { + var vtq = TagMapper.ToVtqMessage("Motor.Speed", 42.5, DateTime.UtcNow, 0); + Assert.Equal("Motor.Speed", vtq.Tag); + Assert.Equal("42.5", vtq.Value); + Assert.Equal("Good", vtq.Quality); + Assert.True(vtq.TimestampUtcTicks > 0); + } +}