Files
lmxopcua/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests/TwinCATTemporalCodecTests.cs
T

84 lines
3.5 KiB
C#

using Shouldly;
using Xunit;
namespace ZB.MOM.WW.OtOpcUa.Driver.TwinCAT.Tests;
/// <summary>#306 — TIME / DATE / DT / TOD decode and encode round trips on known values.</summary>
[Trait("Category", "Unit")]
public sealed class TwinCATTemporalCodecTests
{
private static readonly DateTime Dt = new(2026, 10, 8, 12, 0, 0, DateTimeKind.Utc);
[Fact]
public void Dt_decodes_seconds_since_1970_to_utc()
{
var raw = (uint)(Dt - DateTime.UnixEpoch).TotalSeconds;
var v = (DateTime)TwinCATTemporalCodec.Decode(TwinCATDataType.DateTime, raw);
v.ShouldBe(Dt);
v.Kind.ShouldBe(DateTimeKind.Utc);
TwinCATTemporalCodec.Encode(TwinCATDataType.DateTime, v).ShouldBe(raw);
}
[Fact]
public void Date_round_trips_and_truncates_to_day()
{
var day = new DateTime(2026, 10, 8, 0, 0, 0, DateTimeKind.Utc);
var raw = TwinCATTemporalCodec.Encode(TwinCATDataType.Date, day);
((DateTime)TwinCATTemporalCodec.Decode(TwinCATDataType.Date, raw)).ShouldBe(day);
TwinCATTemporalCodec.Encode(TwinCATDataType.Date, day.AddHours(13)).ShouldBe(raw);
}
[Fact]
public void Tod_round_trips_milliseconds()
{
const uint ms = (12 * 3600 + 30 * 60 + 15) * 1000 + 250; // TOD#12:30:15.250
TwinCATTemporalCodec.Decode(TwinCATDataType.TimeOfDay, ms).ShouldBe(45_015_250d);
TwinCATTemporalCodec.Encode(TwinCATDataType.TimeOfDay, 45_015_250d).ShouldBe(ms);
}
[Fact]
public void Time_round_trips_milliseconds_and_accepts_timespan()
{
const uint ms = ((1 * 60 + 2) * 60 + 3) * 1000 + 4; // T#1h2m3s4ms
TwinCATTemporalCodec.Decode(TwinCATDataType.Time, ms).ShouldBe(3_723_004d);
TwinCATTemporalCodec.Encode(TwinCATDataType.Time, 3_723_004d).ShouldBe(ms);
TwinCATTemporalCodec.Encode(TwinCATDataType.Time, new TimeSpan(0, 1, 2, 3, 4)).ShouldBe(ms);
TwinCATTemporalCodec.Encode(TwinCATDataType.Time, 5u).ShouldBe(5u); // raw integer still writes
}
[Theory]
[InlineData(-1d)]
[InlineData(double.NaN)]
[InlineData(5_000_000_000d)]
public void Time_encode_refuses_out_of_range(double value)
=> Should.Throw<OverflowException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.Time, value));
[Theory]
[InlineData(86_400_000d)]
[InlineData(-1d)]
public void Tod_encode_refuses_a_full_day_or_more(double value)
=> Should.Throw<OverflowException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.TimeOfDay, value));
[Fact]
public void Dt_before_1970_is_refused()
=> Should.Throw<OverflowException>(() =>
TwinCATTemporalCodec.Encode(TwinCATDataType.DateTime, new DateTime(1960, 1, 1, 0, 0, 0, DateTimeKind.Utc)));
[Fact]
public void Wrong_shape_is_a_type_mismatch()
{
Should.Throw<InvalidCastException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.Time, Dt));
Should.Throw<InvalidCastException>(() => TwinCATTemporalCodec.Encode(TwinCATDataType.DateTime, TimeSpan.FromSeconds(1)));
}
[Fact]
public void Client_write_conversion_routes_temporal_types_through_the_codec()
{
AdsTwinCATClient.ConvertForWrite(TwinCATDataType.Time, 1000d).ShouldBe(1000u);
AdsTwinCATClient.ConvertForWrite(TwinCATDataType.DateTime, Dt)
.ShouldBe((uint)(Dt - DateTime.UnixEpoch).TotalSeconds);
// Authored UDInt against a TIME symbol keeps the raw counter.
AdsTwinCATClient.ConvertForWrite(TwinCATDataType.UDInt, 1000u).ShouldBe(1000u);
}
}