Fix Galaxy paging review findings

This commit is contained in:
Joseph Doherty
2026-04-29 11:59:49 -04:00
parent d543679044
commit ac2787f619
14 changed files with 261 additions and 22 deletions
@@ -814,9 +814,7 @@ public static class MxGatewayClientCli
TextWriter output,
CancellationToken cancellationToken)
{
DiscoverHierarchyReply reply = await client.GalaxyDiscoverHierarchyAsync(
new DiscoverHierarchyRequest(),
cancellationToken)
DiscoverHierarchyReply reply = await DiscoverAllGalaxyHierarchyAsync(client, cancellationToken)
.ConfigureAwait(false);
if (arguments.HasFlag("json"))
@@ -834,6 +832,39 @@ public static class MxGatewayClientCli
return 0;
}
private static async Task<DiscoverHierarchyReply> DiscoverAllGalaxyHierarchyAsync(
IMxGatewayCliClient client,
CancellationToken cancellationToken)
{
DiscoverHierarchyReply aggregate = new();
HashSet<string> seenPageTokens = new(StringComparer.Ordinal);
string pageToken = string.Empty;
do
{
DiscoverHierarchyReply page = await client.GalaxyDiscoverHierarchyAsync(
new DiscoverHierarchyRequest
{
PageSize = 5000,
PageToken = pageToken,
},
cancellationToken)
.ConfigureAwait(false);
aggregate.Objects.Add(page.Objects);
aggregate.TotalObjectCount = page.TotalObjectCount;
pageToken = page.NextPageToken;
if (!string.IsNullOrWhiteSpace(pageToken)
&& !seenPageTokens.Add(pageToken))
{
throw new MxGatewayException(
$"Galaxy DiscoverHierarchy returned a repeated page token '{pageToken}'.");
}
}
while (!string.IsNullOrWhiteSpace(pageToken));
return aggregate;
}
private static async Task<int> GalaxyWatchAsync(
CliArguments arguments,
IMxGatewayCliClient client,
@@ -140,6 +140,26 @@ public sealed class GalaxyRepositoryClientTests
Assert.False(call.CallOptions.CancellationToken.IsCancellationRequested);
}
[Fact]
public async Task DiscoverHierarchyAsync_WithRepeatedPageToken_ThrowsProtocolError()
{
FakeGalaxyRepositoryTransport transport = CreateTransport();
transport.DiscoverHierarchyReplies.Enqueue(new DiscoverHierarchyReply
{
NextPageToken = "7:1",
});
transport.DiscoverHierarchyReplies.Enqueue(new DiscoverHierarchyReply
{
NextPageToken = "7:1",
});
await using GalaxyRepositoryClient client = CreateClient(transport);
MxGatewayException exception = await Assert.ThrowsAsync<MxGatewayException>(
async () => await client.DiscoverHierarchyAsync());
Assert.Contains("repeated page token", exception.Message, StringComparison.Ordinal);
}
[Fact]
public async Task TestConnectionAsync_RetriesOnTransientGrpcFailure()
{
@@ -207,8 +207,10 @@ public sealed class MxGatewayClientCliTests
using var output = new StringWriter();
using var error = new StringWriter();
FakeCliClient fakeClient = new();
fakeClient.GalaxyDiscoverHierarchyReply = new DiscoverHierarchyReply
fakeClient.GalaxyDiscoverHierarchyReplies.Enqueue(new DiscoverHierarchyReply
{
NextPageToken = "7:1",
TotalObjectCount = 2,
Objects =
{
new GalaxyObject
@@ -227,7 +229,21 @@ public sealed class MxGatewayClientCliTests
},
},
},
};
});
fakeClient.GalaxyDiscoverHierarchyReplies.Enqueue(new DiscoverHierarchyReply
{
TotalObjectCount = 2,
Objects =
{
new GalaxyObject
{
GobjectId = 8,
TagName = "DelmiaReceiver_002",
ContainedName = "DelmiaReceiver",
ParentGobjectId = 1,
},
},
});
int exitCode = await MxGatewayClientCli.RunAsync(
[
@@ -242,10 +258,14 @@ public sealed class MxGatewayClientCliTests
_ => fakeClient);
Assert.Equal(0, exitCode);
Assert.Single(fakeClient.GalaxyDiscoverHierarchyRequests);
Assert.Equal(2, fakeClient.GalaxyDiscoverHierarchyRequests.Count);
Assert.Equal(5000, fakeClient.GalaxyDiscoverHierarchyRequests[0].PageSize);
Assert.Equal("", fakeClient.GalaxyDiscoverHierarchyRequests[0].PageToken);
Assert.Equal("7:1", fakeClient.GalaxyDiscoverHierarchyRequests[1].PageToken);
string text = output.ToString();
Assert.Contains("objects=1", text);
Assert.Contains("objects=2", text);
Assert.Contains("DelmiaReceiver_001", text);
Assert.Contains("DelmiaReceiver_002", text);
Assert.Contains("attributes=1", text);
Assert.Equal(string.Empty, error.ToString());
}
@@ -411,6 +431,8 @@ public sealed class MxGatewayClientCliTests
public DiscoverHierarchyReply GalaxyDiscoverHierarchyReply { get; set; } = new();
public Queue<DiscoverHierarchyReply> GalaxyDiscoverHierarchyReplies { get; } = new();
public List<TestConnectionRequest> GalaxyTestConnectionRequests { get; } = [];
public List<GetLastDeployTimeRequest> GalaxyGetLastDeployTimeRequests { get; } = [];
@@ -438,7 +460,10 @@ public sealed class MxGatewayClientCliTests
CancellationToken cancellationToken)
{
GalaxyDiscoverHierarchyRequests.Add(request);
return Task.FromResult(GalaxyDiscoverHierarchyReply);
return Task.FromResult(
GalaxyDiscoverHierarchyReplies.TryDequeue(out DiscoverHierarchyReply? reply)
? reply
: GalaxyDiscoverHierarchyReply);
}
public List<WatchDeployEventsRequest> GalaxyWatchDeployEventsRequests { get; } = [];
@@ -146,6 +146,7 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable
public async Task<IReadOnlyList<GalaxyObject>> DiscoverHierarchyAsync(CancellationToken cancellationToken = default)
{
List<GalaxyObject> objects = [];
HashSet<string> seenPageTokens = new(StringComparer.Ordinal);
string pageToken = string.Empty;
do
{
@@ -160,6 +161,12 @@ public sealed class GalaxyRepositoryClient : IAsyncDisposable
objects.AddRange(reply.Objects);
pageToken = reply.NextPageToken;
if (!string.IsNullOrWhiteSpace(pageToken)
&& !seenPageTokens.Add(pageToken))
{
throw new MxGatewayException(
$"Galaxy DiscoverHierarchy returned a repeated page token '{pageToken}'.");
}
}
while (!string.IsNullOrWhiteSpace(pageToken));