test(ablegacy): bit write surfaces device rejection status (review)
Adds `Bit_write_surfaces_device_rejection_status` to AbLegacyBitRmwTests, verifying that a non-zero libplctag status returned by the parent-word write in WriteBitInWordAsync propagates as a non-Good OPC UA StatusCode rather than being silently swallowed. Added a minimal `WriteStatusOverride` hook to FakeAbLegacyTag (test-project-only) so the read half of the RMW still returns 0/Good while the write half returns the seeded error code.
This commit is contained in:
@@ -177,4 +177,38 @@ public sealed class AbLegacyBitRmwTests
|
||||
|
||||
Convert.ToInt32(factory.Tags["B3:0"].Value).ShouldBe(0xFF);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A non-zero libplctag status returned by the parent-word write is surfaced as a non-Good
|
||||
/// OPC UA StatusCode — the PCCC device rejection propagates to the caller.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Bit_write_surfaces_device_rejection_status()
|
||||
{
|
||||
// Arrange: the parent tag reads OK (Status = 0) but write returns a timeout error.
|
||||
const int errorTimeout = (int)libplctag.Status.ErrorTimeout;
|
||||
var factory = new FakeAbLegacyTagFactory
|
||||
{
|
||||
Customise = p => new FakeAbLegacyTag(p)
|
||||
{
|
||||
Value = (short)0b0001,
|
||||
WriteStatusOverride = errorTimeout,
|
||||
},
|
||||
};
|
||||
var drv = new AbLegacyDriver(new AbLegacyDriverOptions
|
||||
{
|
||||
Devices = [new AbLegacyDeviceOptions("ab://10.0.0.5/1,0")],
|
||||
Tags = [new AbLegacyTagDefinition("F", "ab://10.0.0.5/1,0", "I:0/3", AbLegacyDataType.Bit)],
|
||||
Probe = new AbLegacyProbeOptions { Enabled = false },
|
||||
}, "drv-1", factory);
|
||||
await drv.InitializeAsync("{}", CancellationToken.None);
|
||||
|
||||
// Act
|
||||
var results = await drv.WriteAsync([new WriteRequest("F", true)], CancellationToken.None);
|
||||
|
||||
// Assert: the write rejection must NOT be silently swallowed as Good.
|
||||
var statusCode = results.Single().StatusCode;
|
||||
statusCode.ShouldNotBe(AbLegacyStatusMapper.Good);
|
||||
statusCode.ShouldBe(AbLegacyStatusMapper.MapLibplctagStatus(errorTimeout));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,9 +17,16 @@ internal class FakeAbLegacyTag : IAbLegacyTagRuntime
|
||||
/// </summary>
|
||||
public object? ArrayValue { get; set; }
|
||||
|
||||
/// <summary>Gets or sets the tag status code.</summary>
|
||||
/// <summary>Gets or sets the tag status code (returned by <see cref="GetStatus"/> for reads).</summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets an optional status code that overrides <see cref="Status"/> once a write
|
||||
/// has been performed (i.e. after <see cref="WriteCount"/> becomes > 0). Lets tests seed
|
||||
/// a write-rejection status without also failing the preceding read.
|
||||
/// </summary>
|
||||
public int? WriteStatusOverride { get; set; }
|
||||
|
||||
/// <summary>Gets or sets a value indicating whether to throw on initialization.</summary>
|
||||
public bool ThrowOnInitialize { get; set; }
|
||||
|
||||
@@ -80,7 +87,8 @@ internal class FakeAbLegacyTag : IAbLegacyTagRuntime
|
||||
|
||||
/// <summary>Gets the current tag status.</summary>
|
||||
/// <returns>The status code.</returns>
|
||||
public virtual int GetStatus() => Status;
|
||||
public virtual int GetStatus() =>
|
||||
WriteStatusOverride.HasValue && WriteCount > 0 ? WriteStatusOverride.Value : Status;
|
||||
|
||||
/// <summary>Decodes the tag value based on the specified data type and bit index.</summary>
|
||||
/// <param name="type">The AbLegacy data type.</param>
|
||||
|
||||
Reference in New Issue
Block a user