64e3fbe035
v2-ci / build (push) Failing after 1m43s
v2-ci / unit-tests (tests/Core/ZB.MOM.WW.OtOpcUa.Cluster.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.ControlPlane.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests) (push) Has been skipped
v2-ci / unit-tests (tests/Server/ZB.MOM.WW.OtOpcUa.Security.Tests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.Host.IntegrationTests) (push) Has been skipped
v2-ci / integration (tests/Server/ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests) (push) Has been skipped
Adds <summary>, <param>, <typeparam>, and <inheritdoc/> tags to public members surfaced by commentchecker — resolves 5,847 of 5,869 issues (99.6%) across three /fixdocs passes.
115 lines
4.9 KiB
C#
115 lines
4.9 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Modbus.Tests;
|
|
|
|
/// <summary>
|
|
/// Regression coverage for Driver.Modbus-009: two configuration edge cases that previously
|
|
/// silently produced wrong wire behaviour.
|
|
/// (1) <c>StringLength = 0</c> for a <c>String</c>-typed tag — used to flow into an FC03
|
|
/// with quantity 0, a spec-illegal request the PLC rejects with exception 03. Now bind-time
|
|
/// validation in <c>ModbusDriverFactoryExtensions</c> rejects the misconfiguration with a
|
|
/// clear diagnostic.
|
|
/// (2) Sub-second <see cref="TimeSpan"/> values on <c>ModbusKeepAliveOptions.Time</c> /
|
|
/// <c>Interval</c> — the int-cast in <c>EnableKeepAlive</c> truncated <c>500 ms</c> to
|
|
/// <c>0</c>, which most OSes interpret as "use the default", silently defeating the
|
|
/// configured timing. <c>ModbusTcpTransport.ClampToWholeSeconds</c> rounds up to a minimum
|
|
/// of 1 second.
|
|
/// </summary>
|
|
[Trait("Category", "Unit")]
|
|
public sealed class ModbusEdgeCaseValidationTests
|
|
{
|
|
/// <summary>Verifies that string tags with zero length are rejected during factory creation.</summary>
|
|
[Fact]
|
|
public void Factory_rejects_String_tag_with_StringLength_zero_via_structured_form()
|
|
{
|
|
const string json = """
|
|
{
|
|
"host": "10.0.0.10",
|
|
"tags": [
|
|
{ "name": "Greeting", "region": "HoldingRegisters", "address": 100, "dataType": "String", "stringLength": 0 }
|
|
]
|
|
}
|
|
""";
|
|
var ex = Should.Throw<InvalidOperationException>(
|
|
() => ModbusDriverFactoryExtensions.CreateInstance("modbus-1", json));
|
|
ex.Message.ShouldContain("StringLength");
|
|
ex.Message.ShouldContain("Greeting");
|
|
}
|
|
|
|
/// <summary>Verifies that omitted string length defaults to zero and is rejected.</summary>
|
|
[Fact]
|
|
public void Factory_rejects_String_tag_with_StringLength_zero_via_missing_field()
|
|
{
|
|
// No stringLength → defaults to 0. Same misconfiguration via a different DTO shape.
|
|
const string json = """
|
|
{
|
|
"host": "10.0.0.10",
|
|
"tags": [
|
|
{ "name": "Greeting", "region": "HoldingRegisters", "address": 100, "dataType": "String" }
|
|
]
|
|
}
|
|
""";
|
|
var ex = Should.Throw<InvalidOperationException>(
|
|
() => ModbusDriverFactoryExtensions.CreateInstance("modbus-1", json));
|
|
ex.Message.ShouldContain("StringLength");
|
|
}
|
|
|
|
/// <summary>Verifies that string tags with length one are accepted.</summary>
|
|
[Fact]
|
|
public void Factory_accepts_String_tag_with_StringLength_one()
|
|
{
|
|
const string json = """
|
|
{
|
|
"host": "10.0.0.10",
|
|
"tags": [
|
|
{ "name": "Greeting", "region": "HoldingRegisters", "address": 100, "dataType": "String", "stringLength": 1 }
|
|
]
|
|
}
|
|
""";
|
|
Should.NotThrow(() => ModbusDriverFactoryExtensions.CreateInstance("modbus-1", json));
|
|
}
|
|
|
|
/// <summary>Verifies that non-string tags are unaffected by string length zero.</summary>
|
|
[Fact]
|
|
public void Factory_accepts_non_String_tag_with_StringLength_zero()
|
|
{
|
|
// The validation only kicks in for String tags — Int16 tags with StringLength=0 are normal.
|
|
const string json = """
|
|
{
|
|
"host": "10.0.0.10",
|
|
"tags": [
|
|
{ "name": "Level", "region": "HoldingRegisters", "address": 100, "dataType": "Int16" }
|
|
]
|
|
}
|
|
""";
|
|
Should.NotThrow(() => ModbusDriverFactoryExtensions.CreateInstance("modbus-1", json));
|
|
}
|
|
|
|
/// <summary>Verifies that sub-second time spans are rounded up to at least one second.</summary>
|
|
/// <param name="ms">The input duration in milliseconds.</param>
|
|
/// <param name="expected">The expected clamped value in whole seconds.</param>
|
|
[Theory]
|
|
[InlineData(0, 1)] // zero clamps up to 1
|
|
[InlineData(500, 1)] // 500 ms rounds up to 1
|
|
[InlineData(999, 1)] // just under 1s rounds up to 1
|
|
[InlineData(1_000, 1)] // exactly 1s passes through
|
|
[InlineData(1_500, 2)] // 1.5s rounds up to 2
|
|
[InlineData(30_000, 30)] // historical PR 53 default — unchanged
|
|
[InlineData(60_000, 60)]
|
|
public void ClampToWholeSeconds_rounds_up_to_at_least_one_second(int ms, int expected)
|
|
{
|
|
ModbusTcpTransport.ClampToWholeSeconds(TimeSpan.FromMilliseconds(ms)).ShouldBe(expected);
|
|
}
|
|
|
|
/// <summary>Verifies that negative time spans are treated as one second.</summary>
|
|
[Fact]
|
|
public void ClampToWholeSeconds_treats_negative_TimeSpan_as_one_second()
|
|
{
|
|
// Defensive — operators occasionally configure a negative TimeSpan thinking it disables
|
|
// the feature. The OS would reject the negative int — clamping to 1 keeps the socket
|
|
// valid until the operator fixes the config.
|
|
ModbusTcpTransport.ClampToWholeSeconds(TimeSpan.FromSeconds(-5)).ShouldBe(1);
|
|
}
|
|
}
|