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:
Joseph Doherty
2026-05-22 09:30:54 -04:00
parent cec7ab6ec4
commit 17432bb1a4
2 changed files with 34 additions and 29 deletions

View File

@@ -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]