6dedf5158f
Review 2026-10-21 G-5, G-6, G-8 ("wire them all").
- G-5: GatewayGalaxyHierarchySource now pages the DiscoverHierarchy RPC
itself (DiscoverHierarchyRawAsync) at Repository.DiscoverPageSize. The
mxaccessgw client's typed DiscoverHierarchyAsync overloads hard-code a
5000-row page and DiscoverHierarchyOptions carries no page size, so the
authored knob reached nothing; the raw request takes page_size on the
wire, so it is honoured here (non-positive → 5000). The loop keeps the
client's repeated-page-token guard.
- G-6: GalaxyDriverProbe reads ProbeTimeoutSeconds from the config and
runs under min(caller budget, configured cap) — the knob can shorten a
probe, never extend it past the host's budget. The TCP preflight now
shares that deadline instead of hanging on the caller's token alone.
- G-8: GatewayGalaxyDataWriter runs a batch as one lane per distinct tag,
concurrently and bounded at 8 (MaxConcurrentWriteLanes); same-tag
writes keep their authored order inside one lane and results land at
their request's index. Acks stay serial by design.
- GalaxyDriverForm: help text now says what the two knobs actually do.
Refs #735
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
93 lines
3.6 KiB
C#
93 lines
3.6 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
using ZB.MOM.WW.MxGateway.Contracts.Proto.Galaxy;
|
|
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Browse;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Tests.Browse;
|
|
|
|
/// <summary>
|
|
/// Review 2026-10-21 G-5: <c>Repository.DiscoverPageSize</c> was authored and parsed but reached
|
|
/// nothing — the client's typed <c>DiscoverHierarchyAsync</c> hard-codes 5000. The source now pages
|
|
/// the raw RPC itself at the configured size.
|
|
/// </summary>
|
|
public sealed class GatewayGalaxyHierarchySourcePagingTests
|
|
{
|
|
private static GalaxyObject Obj(int id) => new() { GobjectId = id, TagName = $"Obj{id}" };
|
|
|
|
/// <summary>Every request carries the configured page size and the page tokens chain until the last page.</summary>
|
|
[Fact]
|
|
public async Task Pages_at_the_configured_size_and_chains_tokens()
|
|
{
|
|
var requests = new List<DiscoverHierarchyRequest>();
|
|
Task<DiscoverHierarchyReply> Discover(DiscoverHierarchyRequest req, CancellationToken ct)
|
|
{
|
|
requests.Add(req);
|
|
var reply = new DiscoverHierarchyReply();
|
|
switch (req.PageToken)
|
|
{
|
|
case "":
|
|
reply.Objects.Add([Obj(1), Obj(2)]);
|
|
reply.NextPageToken = "p2";
|
|
break;
|
|
case "p2":
|
|
reply.Objects.Add([Obj(3), Obj(4)]);
|
|
reply.NextPageToken = "p3";
|
|
break;
|
|
default:
|
|
reply.Objects.Add(Obj(5));
|
|
reply.NextPageToken = "";
|
|
break;
|
|
}
|
|
return Task.FromResult(reply);
|
|
}
|
|
|
|
var objects = await GatewayGalaxyHierarchySource.PageAsync(Discover, pageSize: 2, CancellationToken.None);
|
|
|
|
objects.Select(o => o.GobjectId).ShouldBe([1, 2, 3, 4, 5]);
|
|
requests.Count.ShouldBe(3);
|
|
requests.ShouldAllBe(r => r.PageSize == 2);
|
|
requests.Select(r => r.PageToken).ShouldBe(["", "p2", "p3"]);
|
|
}
|
|
|
|
/// <summary>A different configured size is what goes on the wire (positive control for the knob).</summary>
|
|
[Fact]
|
|
public async Task A_different_page_size_is_sent_on_the_wire()
|
|
{
|
|
int? sent = null;
|
|
Task<DiscoverHierarchyReply> Discover(DiscoverHierarchyRequest req, CancellationToken ct)
|
|
{
|
|
sent = req.PageSize;
|
|
return Task.FromResult(new DiscoverHierarchyReply());
|
|
}
|
|
|
|
await GatewayGalaxyHierarchySource.PageAsync(Discover, pageSize: 250, CancellationToken.None);
|
|
sent.ShouldBe(250);
|
|
}
|
|
|
|
/// <summary>A non-positive configured size falls back to the client library's 5000 default.</summary>
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-7)]
|
|
public void Non_positive_page_size_falls_back_to_the_default(int configured)
|
|
=> GatewayGalaxyHierarchySource.EffectivePageSize(configured).ShouldBe(GatewayGalaxyHierarchySource.DefaultPageSize);
|
|
|
|
/// <summary>A repeated page token is refused rather than walked forever.</summary>
|
|
[Fact]
|
|
public async Task A_repeated_page_token_is_refused()
|
|
{
|
|
var calls = 0;
|
|
Task<DiscoverHierarchyReply> Discover(DiscoverHierarchyRequest req, CancellationToken ct)
|
|
{
|
|
calls++;
|
|
var reply = new DiscoverHierarchyReply { NextPageToken = "same" };
|
|
reply.Objects.Add(Obj(calls));
|
|
return Task.FromResult(reply);
|
|
}
|
|
|
|
var ex = await Should.ThrowAsync<InvalidOperationException>(
|
|
() => GatewayGalaxyHierarchySource.PageAsync(Discover, pageSize: 10, CancellationToken.None));
|
|
ex.Message.ShouldContain("repeated page token");
|
|
calls.ShouldBe(2);
|
|
}
|
|
}
|