using System.Text;
using Shouldly;
using Xunit;
using ZB.MOM.WW.OtOpcUa.Driver.TwinCAT;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests;
///
/// Pins — how an IEC STRING(n) / WSTRING(n) buffer,
/// read at the PLC's own declared width, becomes a value. Ported from the ignitionoee TwinCAT
/// driver (0.19.3 first-NUL STRING, 0.19.4 TwinCAT-007 first-NUL WSTRING and TwinCAT-010
/// Windows-1252), whose byte fixtures these reuse.
///
[Trait("Category", "Unit")]
public sealed class TwinCATStringCodecTests
{
///
/// An IEC STRING ends at its FIRST NUL; the buffer beyond keeps whatever a longer earlier value
/// left there. Live on the Elma cleaner Z33856 (2026-09-24) a decoder that only trimmed trailing
/// NULs published work order 67504440 as "67504440\0" + "1" and part
/// 110032285 as "110032285\0" + "1\0" + "30" — a wrong value at Good quality.
///
[Fact]
public void String_stops_at_the_first_nul_and_ignores_stale_bytes_after_it()
{
byte[] workOrder = [(byte)'6', (byte)'7', (byte)'5', (byte)'0', (byte)'4', (byte)'4', (byte)'4', (byte)'0',
0x00, (byte)'1', 0x00, (byte)'3', (byte)'0', 0x00];
TwinCATStringCodec.DecodeString(workOrder, utf8: false).ShouldBe("67504440");
byte[] emptied = [0x00, (byte)'O', (byte)'L', (byte)'D'];
TwinCATStringCodec.DecodeString(emptied, utf8: false).ShouldBe("");
}
/// A buffer with no NUL at all (a full-length value) decodes whole.
[Fact]
public void String_without_a_nul_decodes_the_whole_buffer()
=> TwinCATStringCodec.DecodeString("ABC"u8.ToArray(), utf8: false).ShouldBe("ABC");
///
/// An IEC STRING on TwinCAT is single-byte Windows-1252, not ASCII and not the host's culture
/// code page: ° (0xB0, an Elma alarm text), ä (0xE4) and € (0x80, the one
/// byte where 1252 and ISO-8859-1 differ) must decode to themselves on any host.
///
[Fact]
public void String_decodes_windows_1252_high_bytes()
{
byte[] text = [(byte)'8', (byte)'0', 0xB0, (byte)'C', (byte)' ', 0xE4, 0x80, 0x00, 0xFF];
TwinCATStringCodec.DecodeString(text, utf8: false).ShouldBe("80°C ä€");
}
///
/// A TwinCAT 3.1 build 4026+ project with "UTF8 Encoding for STRING" on reports UTF-8 string
/// data; the codec honours it (still ending at the first NUL).
///
[Fact]
public void String_decodes_utf8_when_the_target_reports_utf8_strings()
{
var bytes = Encoding.UTF8.GetBytes("80°C ä€").Concat(new byte[] { 0x00, (byte)'X' }).ToArray();
TwinCATStringCodec.DecodeString(bytes, utf8: true).ShouldBe("80°C ä€");
}
///
/// A WSTRING ends at its first ALIGNED U+0000 code unit (TwinCAT-007): "NEW\0LD"
/// must publish "NEW". A zero HIGH byte of a non-NUL unit (every ASCII character in
/// UTF-16LE) is not a terminator.
///
[Fact]
public void WString_stops_at_the_first_aligned_nul_code_unit()
{
var bytes = Encoding.Unicode.GetBytes("NEW\0LD\0");
TwinCATStringCodec.DecodeWString(bytes).ShouldBe("NEW");
TwinCATStringCodec.DecodeWString(Encoding.Unicode.GetBytes("Grüße")).ShouldBe("Grüße");
}
/// A trailing odd byte is not half a character — it is ignored.
[Fact]
public void WString_ignores_a_trailing_odd_byte()
{
var bytes = Encoding.Unicode.GetBytes("AB").Concat(new byte[] { 0x43 }).ToArray();
TwinCATStringCodec.DecodeWString(bytes).ShouldBe("AB");
}
}