test(galaxy): live-gw smoke — writer borrows subscription handle, skips AddItem
Subscribe a real tag, register its gateway item handle, write via the registry-wired writer: asserts the borrowed-handle write commits Good with AddItemCallCount==0 (control with no source: ==1). Proves the subscription handle is usable for a committing no-login supervisory write. Skip-gated on MXGW_ENDPOINT + GALAXY_MXGW_API_KEY; verified live vs 10.100.0.48:5120 (3/3).
This commit is contained in:
+49
@@ -124,6 +124,55 @@ public sealed class GatewayGalaxyLiveReopenAndWriteTests
|
||||
$"write-persist smoke: {WriteRef} {current} -> wrote {target} -> fresh-session read-back {persisted}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <b>Borrow</b> — the writer reuses a live MXAccess item handle the subscription registry
|
||||
/// already holds, so the first write to an already-subscribed tag skips a redundant AddItem.
|
||||
/// Proves the load-bearing premise (a subscription handle is usable for a no-login supervisory
|
||||
/// write that commits) AND the optimization (zero AddItem on the borrow path). Control: a writer
|
||||
/// with no registry source on the same session AddItems exactly once.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public async Task Live_writer_borrows_subscription_handle_and_skips_AddItem()
|
||||
{
|
||||
var (endpoint, apiKey) = RequireLiveGatewayOrSkip();
|
||||
var ct = TestContext.Current.CancellationToken;
|
||||
var clientOptions = BuildClientOptions(endpoint, apiKey);
|
||||
|
||||
await using var session = new GalaxyMxSession(new GalaxyMxAccessOptions(ClientName: "OtOpcUaBorrowSmoke"));
|
||||
await session.ConnectAsync(clientOptions, ct);
|
||||
|
||||
// Subscribe the tag so the registry holds the gateway's REAL item handle for it.
|
||||
var subscriber = new GatewayGalaxySubscriber(session);
|
||||
var subResults = await subscriber.SubscribeBulkAsync([WriteRef], bufferedUpdateIntervalMs: 0, ct);
|
||||
var match = subResults.SingleOrDefault(r =>
|
||||
string.Equals(r.TagAddress, WriteRef, StringComparison.OrdinalIgnoreCase) && r.WasSuccessful);
|
||||
match.ShouldNotBeNull($"the gateway should accept a subscription on {WriteRef}");
|
||||
match!.ItemHandle.ShouldBeGreaterThan(0, "a successful subscribe must return a positive item handle");
|
||||
|
||||
var registry = new SubscriptionRegistry();
|
||||
registry.Register(1, [new TagBinding(WriteRef, match.ItemHandle)]);
|
||||
|
||||
// Borrow case: a writer wired to the registry must commit the write WITHOUT a fresh AddItem,
|
||||
// and must NOT cache the borrowed handle (the registry owns its lifecycle).
|
||||
var borrowingWriter = new GatewayGalaxyDataWriter(
|
||||
session, writeUserId: 0, logger: null, subscribedHandleSource: registry.TryResolveItemHandle);
|
||||
var borrowed = await borrowingWriter.WriteAsync(
|
||||
[new WriteRequest(WriteRef, 2727.0f)], _ => SecurityClassification.FreeAccess, ct);
|
||||
borrowed.ShouldHaveSingleItem().StatusCode.ShouldBe(0u, "the borrowed-handle write should commit Good (0)");
|
||||
borrowingWriter.AddItemCallCount.ShouldBe(0, "the writer should reuse the subscription handle, not AddItem");
|
||||
borrowingWriter.CachedItemHandleCount.ShouldBe(0, "a borrowed handle must not be cached in the writer");
|
||||
|
||||
// Control: a writer with NO source on the same session must AddItem exactly once for the same tag.
|
||||
var plainWriter = new GatewayGalaxyDataWriter(session, writeUserId: 0);
|
||||
var control = await plainWriter.WriteAsync(
|
||||
[new WriteRequest(WriteRef, 3838.0f)], _ => SecurityClassification.FreeAccess, ct);
|
||||
control.ShouldHaveSingleItem().StatusCode.ShouldBe(0u, "the control write should commit Good (0)");
|
||||
plainWriter.AddItemCallCount.ShouldBe(1, "with no borrow source the writer must AddItem once");
|
||||
|
||||
TestContext.Current.SendDiagnosticMessage(
|
||||
$"borrow smoke: subscribed {WriteRef} -> handle {match.ItemHandle}; borrowed write Good with AddItemCallCount=0; control AddItemCallCount=1");
|
||||
}
|
||||
|
||||
private static (string Endpoint, string ApiKey) RequireLiveGatewayOrSkip()
|
||||
{
|
||||
var endpoint = Environment.GetEnvironmentVariable("MXGW_ENDPOINT");
|
||||
|
||||
Reference in New Issue
Block a user