fix(driver-abcip): correct Driver.AbCip-005 approach and fix 014 tests
Finding 005 revised approach: keep the parent Structure tag in `_tagsByName` so the whole-UDT grouping planner can find it (required for Driver.AbCip-003 opt-in path + alarm projection). Instead, detect a direct read of a Structure-with-Members in `ReadSingleAsync` and return `BadNotSupported` rather than Good/null — explicitly documenting the contract that callers must address member paths. Duplicate-key checks (scalar and member fan-out) remain. Finding 014 test corrections: `Structure_parent_tag_read_returns_BadNotSupported` now asserts the new contract. `Read_UDInt_tag_returns_uint_value_not_negative_wrapped_int` assertion fixed to use `ShouldBeOfType<uint>()` instead of `ShouldNotBe(-1)` (Shouldly overflows comparing uint.MaxValue with int). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -223,19 +223,20 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
var results = await drv.ReadAsync(["Counter"], CancellationToken.None);
|
||||
|
||||
results.Single().StatusCode.ShouldBe(AbCipStatusMapper.Good);
|
||||
// The value must be the uint, not a negative int
|
||||
// The value must be the uint — if it were (int)GetUInt32 it would be -1 (wrong type).
|
||||
results.Single().Value.ShouldBe(largeUDInt);
|
||||
results.Single().Value.ShouldNotBe(-1);
|
||||
results.Single().Value.ShouldBeOfType<uint>();
|
||||
}
|
||||
|
||||
// ---- Driver.AbCip-005 — Structure parent not registered; duplicate key check ----
|
||||
|
||||
[Fact]
|
||||
public async Task Structure_parent_tag_is_not_readable_after_member_fan_out()
|
||||
public async Task Structure_parent_tag_read_returns_BadNotSupported_not_Good_null()
|
||||
{
|
||||
// Regression for Driver.AbCip-005: the bare parent "Motor" used to be added to
|
||||
// _tagsByName before member fan-out, so ReadAsync("Motor") returned Good/null.
|
||||
// After the fix, reading the parent name returns BadNodeIdUnknown.
|
||||
// Regression for Driver.AbCip-005: reading the bare parent "Motor" used to return
|
||||
// Good/null because DecodeValue(Structure, ...) returns null. After the fix,
|
||||
// the per-tag read path detects a Structure-with-Members and returns BadNotSupported
|
||||
// so callers know to address individual member paths instead.
|
||||
var factory = new FakeAbCipTagFactory();
|
||||
var drv = new AbCipDriver(new AbCipDriverOptions
|
||||
{
|
||||
@@ -253,8 +254,9 @@ public sealed class AbCipDriverCodeReviewRegressionTests
|
||||
|
||||
var results = await drv.ReadAsync(["Motor"], CancellationToken.None);
|
||||
|
||||
// Parent is not a readable tag — BadNodeIdUnknown, not Good/null.
|
||||
results.Single().StatusCode.ShouldBe(AbCipStatusMapper.BadNodeIdUnknown);
|
||||
// Parent is a container, not a scalar — BadNotSupported, not Good/null.
|
||||
results.Single().StatusCode.ShouldBe(AbCipStatusMapper.BadNotSupported);
|
||||
results.Single().Value.ShouldBeNull();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user