fix(s7): UInt64 box cast + Timer/Counter transient-write returns BadNotWritable (final review)
M1: add missing (object) cast to UInt64 arm of DecodeScalarBlock switch expression, matching the Int64 arm style and the comment that each arm is boxed explicitly. M2: short-circuit Timer/Counter writes in WriteAsync to BadNotWritable before WriteOneAsync, so transient equipment-tag refs (Writable=true from parser) return the same status code as authored tags rejected at init — documented in the docs. Adds 6 pure unit tests pinning the area-detection precondition the guard relies on. EncodeScalarBlock Timer/Counter throws remain as the defensive backstop.
This commit is contained in:
@@ -498,7 +498,10 @@ public sealed class S7ScalarBlockTests
|
||||
|
||||
// ── EncodeScalarBlock — Timer/Counter are read-only this phase ─────────────────────────
|
||||
|
||||
/// <summary>Verifies a Timer write throws NotSupportedException — Timer/Counter are read-only.</summary>
|
||||
/// <summary>Verifies a Timer write throws NotSupportedException — Timer/Counter are read-only.
|
||||
/// This backstop is still exercised even though <see cref="S7Driver.WriteAsync"/> now
|
||||
/// short-circuits to BadNotWritable before reaching EncodeScalarBlock — a mis-route (e.g.
|
||||
/// a future refactor that bypasses the WriteAsync guard) must still fail loudly here.</summary>
|
||||
[Fact]
|
||||
public void EncodeScalarBlock_Timer_throws_read_only()
|
||||
=> Should.Throw<NotSupportedException>(() => S7Driver.EncodeScalarBlock(TimerTag(), 1.0));
|
||||
@@ -507,4 +510,31 @@ public sealed class S7ScalarBlockTests
|
||||
[Fact]
|
||||
public void EncodeScalarBlock_Counter_throws_read_only()
|
||||
=> Should.Throw<NotSupportedException>(() => S7Driver.EncodeScalarBlock(CounterTag(), 42));
|
||||
|
||||
// ── WriteAsync guard — Timer/Counter area precondition ────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// Pins the precondition for the <see cref="S7Driver.WriteAsync"/> Timer/Counter
|
||||
/// short-circuit: <see cref="S7AddressParser.TryParse"/> on a Timer address must
|
||||
/// yield <see cref="S7Area.Timer"/> and on a Counter address must yield
|
||||
/// <see cref="S7Area.Counter"/>. The WriteAsync guard detects the area via TryParse (for
|
||||
/// transient equipment-tag refs not in _parsedByName) and returns BadNotWritable without
|
||||
/// reaching EncodeScalarBlock. This test verifies the area detection that gate depends on
|
||||
/// — a pure, no-PLC assertion. (<see cref="WriteAsync"/> itself requires a connected Plc
|
||||
/// and is therefore integration-tested only; the EncodeScalarBlock_Timer/Counter_throws
|
||||
/// tests above cover the defensive backstop if the guard is ever bypassed.)
|
||||
/// </summary>
|
||||
[Theory]
|
||||
[InlineData("T0", S7Area.Timer)]
|
||||
[InlineData("T5", S7Area.Timer)]
|
||||
[InlineData("T15", S7Area.Timer)]
|
||||
[InlineData("C0", S7Area.Counter)]
|
||||
[InlineData("C3", S7Area.Counter)]
|
||||
[InlineData("C10", S7Area.Counter)]
|
||||
public void AddressParser_yields_Timer_or_Counter_area_for_TC_addresses(string address, S7Area expectedArea)
|
||||
{
|
||||
S7AddressParser.TryParse(address, out var parsed).ShouldBeTrue();
|
||||
parsed.Area.ShouldBe(expectedArea,
|
||||
$"WriteAsync BadNotWritable guard relies on TryParse('{address}').Area == {expectedArea}");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user