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);
|
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>
|
/// </summary>
|
||||||
public object? ArrayValue { get; set; }
|
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; }
|
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>
|
/// <summary>Gets or sets a value indicating whether to throw on initialization.</summary>
|
||||||
public bool ThrowOnInitialize { get; set; }
|
public bool ThrowOnInitialize { get; set; }
|
||||||
|
|
||||||
@@ -80,7 +87,8 @@ internal class FakeAbLegacyTag : IAbLegacyTagRuntime
|
|||||||
|
|
||||||
/// <summary>Gets the current tag status.</summary>
|
/// <summary>Gets the current tag status.</summary>
|
||||||
/// <returns>The status code.</returns>
|
/// <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>
|
/// <summary>Decodes the tag value based on the specified data type and bit index.</summary>
|
||||||
/// <param name="type">The AbLegacy data type.</param>
|
/// <param name="type">The AbLegacy data type.</param>
|
||||||
|
|||||||
Reference in New Issue
Block a user