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;
///
/// Review 2026-10-21 G-5: Repository.DiscoverPageSize was authored and parsed but reached
/// nothing — the client's typed DiscoverHierarchyAsync hard-codes 5000. The source now pages
/// the raw RPC itself at the configured size.
///
public sealed class GatewayGalaxyHierarchySourcePagingTests
{
private static GalaxyObject Obj(int id) => new() { GobjectId = id, TagName = $"Obj{id}" };
/// Every request carries the configured page size and the page tokens chain until the last page.
[Fact]
public async Task Pages_at_the_configured_size_and_chains_tokens()
{
var requests = new List();
Task 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"]);
}
/// A different configured size is what goes on the wire (positive control for the knob).
[Fact]
public async Task A_different_page_size_is_sent_on_the_wire()
{
int? sent = null;
Task Discover(DiscoverHierarchyRequest req, CancellationToken ct)
{
sent = req.PageSize;
return Task.FromResult(new DiscoverHierarchyReply());
}
await GatewayGalaxyHierarchySource.PageAsync(Discover, pageSize: 250, CancellationToken.None);
sent.ShouldBe(250);
}
/// A non-positive configured size falls back to the client library's 5000 default.
[Theory]
[InlineData(0)]
[InlineData(-7)]
public void Non_positive_page_size_falls_back_to_the_default(int configured)
=> GatewayGalaxyHierarchySource.EffectivePageSize(configured).ShouldBe(GatewayGalaxyHierarchySource.DefaultPageSize);
/// A repeated page token is refused rather than walked forever.
[Fact]
public async Task A_repeated_page_token_is_refused()
{
var calls = 0;
Task 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(
() => GatewayGalaxyHierarchySource.PageAsync(Discover, pageSize: 10, CancellationToken.None));
ex.Message.ShouldContain("repeated page token");
calls.ShouldBe(2);
}
}