Compare commits

...

4 Commits

Author SHA1 Message Date
Joseph Doherty b2448510ac client/java: add browseChildrenRejectsRepeatedPageToken test for parity 2026-05-28 15:17:52 -04:00
Joseph Doherty 75610e3f55 client/go: wrap browseChildren duplicate-page-token error in GatewayError 2026-05-28 15:17:10 -04:00
Joseph Doherty 5032166106 client/dotnet: assert failed expand leaves node unexpanded 2026-05-28 15:16:07 -04:00
Joseph Doherty 76a042d663 grpc: make page_token error strings RPC-name-agnostic 2026-05-28 15:15:40 -04:00
5 changed files with 57 additions and 4 deletions
@@ -106,6 +106,8 @@ public sealed class LazyBrowseNodeTests
new RpcException(new Status(StatusCode.NotFound, "Parent not found")))); new RpcException(new Status(StatusCode.NotFound, "Parent not found"))));
await Assert.ThrowsAsync<MxGatewayException>(async () => await roots[0].ExpandAsync()); await Assert.ThrowsAsync<MxGatewayException>(async () => await roots[0].ExpandAsync());
Assert.False(roots[0].IsExpanded);
Assert.Empty(roots[0].Children);
} }
/// <summary> /// <summary>
+4 -1
View File
@@ -375,7 +375,10 @@ func (c *GalaxyClient) browseChildrenInner(
return nodes, nil return nodes, nil
} }
if _, dup := seen[pageToken]; dup { if _, dup := seen[pageToken]; dup {
return nil, fmt.Errorf("mxgateway: galaxy browse children returned repeated page token %q", pageToken) return nil, &GatewayError{
Op: "galaxy browse children",
Err: fmt.Errorf("repeated page token %q", pageToken),
}
} }
seen[pageToken] = struct{}{} seen[pageToken] = struct{}{}
} }
+27
View File
@@ -738,3 +738,30 @@ func TestGalaxyBrowseWithFilterForwardsToRequest(t *testing.T) {
t.Fatal("HistorizedOnly = false, want true") t.Fatal("HistorizedOnly = false, want true")
} }
} }
func TestGalaxyBrowseChildrenRejectsRepeatedPageToken(t *testing.T) {
// Build a reply that carries a non-empty NextPageToken so browseChildrenInner
// will request a second page. Queue the same reply twice so the second response
// returns the same page token, triggering the duplicate-token guard.
page := buildBrowseReply(
[]*pb.GalaxyObject{obj(1, "Plant", true)},
[]bool{true},
1,
)
page.NextPageToken = "1:abc:1"
fake := &fakeGalaxyServer{
browseChildrenReplies: []*pb.BrowseChildrenReply{page, page},
}
client, cleanup := newGalaxyBufconnClient(t, fake)
defer cleanup()
_, err := client.Browse(context.Background(), nil)
if err == nil {
t.Fatal("Browse: error = nil, want repeated-page-token error")
}
var gwErr *GatewayError
if !errors.As(err, &gwErr) {
t.Fatalf("error type = %T, want *GatewayError; err = %v", err, err)
}
}
@@ -203,6 +203,27 @@ final class GalaxyRepositoryClientTests {
} }
} }
@Test
void browseChildrenRejectsRepeatedPageToken() throws Exception {
// Queue the same BrowseChildrenReply twice with a non-empty NextPageToken.
// The client will request a second page and detect that the token repeats.
BrowseChildrenService service = new BrowseChildrenService();
BrowseChildrenReply repeatedReply = browseReply(
List.of(obj(1, "Plant", true)),
List.of(true),
1L,
"1:abc:1");
service.replies.add(repeatedReply);
service.replies.add(repeatedReply);
try (InProcessGalaxy g = InProcessGalaxy.start(service, new AtomicReference<>());
GalaxyRepositoryClient client = g.client("")) {
MxGatewayException error = assertThrows(MxGatewayException.class, client::browse);
assertTrue(error.getMessage().contains("repeated page token"));
}
}
@Test @Test
void watchDeployEventsReceivesEventsInOrder() throws Exception { void watchDeployEventsReceivesEventsInOrder() throws Exception {
DeployEvent first = DeployEvent.newBuilder() DeployEvent first = DeployEvent.newBuilder()
@@ -348,21 +348,21 @@ public sealed class GalaxyRepositoryGrpcService(
{ {
throw new RpcException(new Status( throw new RpcException(new Status(
StatusCode.InvalidArgument, StatusCode.InvalidArgument,
"DiscoverHierarchy page_token is invalid.")); "page_token is invalid."));
} }
if (sequence != currentSequence) if (sequence != currentSequence)
{ {
throw new RpcException(new Status( throw new RpcException(new Status(
StatusCode.InvalidArgument, StatusCode.InvalidArgument,
"DiscoverHierarchy page_token is stale.")); "page_token is stale."));
} }
if (!string.Equals(parts[1], currentFilterSignature, StringComparison.Ordinal)) if (!string.Equals(parts[1], currentFilterSignature, StringComparison.Ordinal))
{ {
throw new RpcException(new Status( throw new RpcException(new Status(
StatusCode.InvalidArgument, StatusCode.InvalidArgument,
"DiscoverHierarchy page_token does not match the current filters.")); "page_token does not match the current filters."));
} }
return new PageToken(sequence, parts[1], offset); return new PageToken(sequence, parts[1], offset);