feat(galaxy): writer borrows live subscription item handles (skip redundant AddItem)
GatewayGalaxyDataWriter now accepts an optional subscribedHandleSource delegate; TryResolveCachedOrBorrowed checks _itemHandles first then the source, so the first write to an already-subscribed tag skips the AddItem round-trip. Borrowed handles are not cached (subscription registry owns lifecycle). AddItemCallCount seam confirms gateway calls.
This commit is contained in:
+93
@@ -88,4 +88,97 @@ public sealed class GatewayGalaxyDataWriterTests
|
||||
writer.CachedItemHandleCount.ShouldBe(0);
|
||||
writer.CachedSupervisedHandleCount.ShouldBe(0);
|
||||
}
|
||||
|
||||
// ===== subscribedHandleSource / TryResolveCachedOrBorrowed tests =====
|
||||
|
||||
/// <summary>
|
||||
/// When a <c>subscribedHandleSource</c> delegate returns a positive handle for the
|
||||
/// requested ref, <see cref="GatewayGalaxyDataWriter.TryResolveCachedOrBorrowed"/> must
|
||||
/// return that handle. Critically, the borrowed handle must NOT be stored in the writer's
|
||||
/// own item-handle cache (the subscription registry owns its lifecycle), and
|
||||
/// <see cref="GatewayGalaxyDataWriter.AddItemCallCount"/> must remain zero (no real
|
||||
/// gateway round-trip issued).
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryResolveCachedOrBorrowed_returns_borrowed_handle_without_caching_it()
|
||||
{
|
||||
var writer = new GatewayGalaxyDataWriter(
|
||||
MinimalSession(), writeUserId: 0, logger: null,
|
||||
subscribedHandleSource: fr => fr == "Tag.X" ? 7 : (int?)null);
|
||||
|
||||
var result = writer.TryResolveCachedOrBorrowed("Tag.X");
|
||||
|
||||
result.ShouldBe(7);
|
||||
writer.CachedItemHandleCount.ShouldBe(0); // borrow NOT stored in _itemHandles
|
||||
writer.AddItemCallCount.ShouldBe(0); // no real AddItem issued
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When a handle is present in the writer's own <c>_itemHandles</c> cache (seeded via
|
||||
/// <see cref="GatewayGalaxyDataWriter.SeedHandleCachesForTest"/>), that cached handle must
|
||||
/// win over the <c>subscribedHandleSource</c> delegate — the writer's own earlier AddItem
|
||||
/// takes priority.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryResolveCachedOrBorrowed_cached_handle_wins_over_source()
|
||||
{
|
||||
// Source would return 99 for "Tag.Y", but the writer already cached handle 42 for it.
|
||||
var writer = new GatewayGalaxyDataWriter(
|
||||
MinimalSession(), writeUserId: 0, logger: null,
|
||||
subscribedHandleSource: fr => fr == "Tag.Y" ? 99 : (int?)null);
|
||||
|
||||
writer.SeedHandleCachesForTest("Tag.Y", itemHandle: 42, supervised: false);
|
||||
|
||||
var result = writer.TryResolveCachedOrBorrowed("Tag.Y");
|
||||
|
||||
result.ShouldBe(42); // cached wins, not 99 from the source
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When no <c>subscribedHandleSource</c> is provided (<c>null</c>), the method must
|
||||
/// return <c>null</c> for a ref that has never been cached — preserving today's
|
||||
/// AddItem-required behavior.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryResolveCachedOrBorrowed_null_source_returns_null_when_not_cached()
|
||||
{
|
||||
var writer = new GatewayGalaxyDataWriter(MinimalSession(), writeUserId: 0);
|
||||
|
||||
var result = writer.TryResolveCachedOrBorrowed("Tag.Z");
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <c>subscribedHandleSource</c> that returns 0 (a sentinel meaning "subscribe
|
||||
/// pending / failed") must be treated as no borrow — the method returns <c>null</c>
|
||||
/// so the caller issues a fresh AddItem.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryResolveCachedOrBorrowed_source_returning_zero_treated_as_no_borrow()
|
||||
{
|
||||
var writer = new GatewayGalaxyDataWriter(
|
||||
MinimalSession(), writeUserId: 0, logger: null,
|
||||
subscribedHandleSource: _ => 0);
|
||||
|
||||
var result = writer.TryResolveCachedOrBorrowed("Tag.W");
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A <c>subscribedHandleSource</c> that returns a negative value (another failure
|
||||
/// sentinel) must also be treated as no borrow — the method returns <c>null</c>.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TryResolveCachedOrBorrowed_source_returning_negative_treated_as_no_borrow()
|
||||
{
|
||||
var writer = new GatewayGalaxyDataWriter(
|
||||
MinimalSession(), writeUserId: 0, logger: null,
|
||||
subscribedHandleSource: _ => -5);
|
||||
|
||||
var result = writer.TryResolveCachedOrBorrowed("Tag.V");
|
||||
|
||||
result.ShouldBeNull();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user