diff --git a/clients/go/mxgateway/galaxy.go b/clients/go/mxgateway/galaxy.go index acac8d1..0ff658d 100644 --- a/clients/go/mxgateway/galaxy.go +++ b/clients/go/mxgateway/galaxy.go @@ -18,6 +18,11 @@ import ( // browseChildrenPageSize is the per-request page size used by the lazy walker. const browseChildrenPageSize = 500 +// discoverHierarchyPageSize is the per-request page size used by DiscoverHierarchy. +// Mirrors the .NET client constant so large galaxies are not silently truncated +// by the server's default page cap. +const discoverHierarchyPageSize = 5000 + // RawGalaxyRepositoryClient is the generated gRPC client interface for the // Galaxy Repository service exposed for callers that need direct contract // access. @@ -155,16 +160,35 @@ func (c *GalaxyClient) GetLastDeployTime(ctx context.Context) (time.Time, bool, // DiscoverHierarchy returns the deployed Galaxy object hierarchy with each // object's dynamic attributes. The objects are returned in the order supplied -// by the server. +// by the server. The call pages over the server's NextPageToken until the +// server signals it has no more results, matching the .NET client. func (c *GalaxyClient) DiscoverHierarchy(ctx context.Context) ([]*GalaxyObject, error) { - callCtx, cancel := c.callContext(ctx) - defer cancel() - - reply, err := c.raw.DiscoverHierarchy(callCtx, &pb.DiscoverHierarchyRequest{}) - if err != nil { - return nil, &GatewayError{Op: "galaxy discover hierarchy", Err: err} + var objects []*GalaxyObject + pageToken := "" + seen := map[string]struct{}{} + for { + callCtx, cancel := c.callContext(ctx) + reply, err := c.raw.DiscoverHierarchy(callCtx, &pb.DiscoverHierarchyRequest{ + PageSize: discoverHierarchyPageSize, + PageToken: pageToken, + }) + cancel() + if err != nil { + return nil, &GatewayError{Op: "galaxy discover hierarchy", Err: err} + } + objects = append(objects, reply.GetObjects()...) + pageToken = reply.GetNextPageToken() + if pageToken == "" { + return objects, nil + } + if _, dup := seen[pageToken]; dup { + return nil, &GatewayError{ + Op: "galaxy discover hierarchy", + Err: fmt.Errorf("repeated page token %q", pageToken), + } + } + seen[pageToken] = struct{}{} } - return reply.GetObjects(), nil } // WatchDeployEventsRaw starts the generated WatchDeployEvents stream for callers diff --git a/clients/go/mxgateway/galaxy_test.go b/clients/go/mxgateway/galaxy_test.go index 0f0202a..629e812 100644 --- a/clients/go/mxgateway/galaxy_test.go +++ b/clients/go/mxgateway/galaxy_test.go @@ -146,6 +146,47 @@ func TestGalaxyDiscoverHierarchyReturnsObjects(t *testing.T) { } } +func TestGalaxyDiscoverHierarchyPaginatesAcrossMultiplePages(t *testing.T) { + page1 := &pb.DiscoverHierarchyReply{ + Objects: []*pb.GalaxyObject{ + {GobjectId: 1, TagName: "A"}, + {GobjectId: 2, TagName: "B"}, + }, + NextPageToken: "page-2", + TotalObjectCount: 3, + } + page2 := &pb.DiscoverHierarchyReply{ + Objects: []*pb.GalaxyObject{ + {GobjectId: 3, TagName: "C"}, + }, + TotalObjectCount: 3, + } + fake := &fakeGalaxyServer{ + discoverHierarchyReplies: []*pb.DiscoverHierarchyReply{page1, page2}, + } + client, cleanup := newGalaxyBufconnClient(t, fake) + defer cleanup() + + objs, err := client.DiscoverHierarchy(context.Background()) + if err != nil { + t.Fatalf("DiscoverHierarchy: %v", err) + } + if got, want := len(objs), 3; got != want { + t.Fatalf("len(objs) = %d, want %d", got, want) + } + if len(fake.discoverHierarchyCalls) != 2 { + t.Fatalf("expected 2 RPC calls, got %d", len(fake.discoverHierarchyCalls)) + } + if fake.discoverHierarchyCalls[0].GetPageSize() != discoverHierarchyPageSize { + t.Fatalf("first call PageSize = %d, want %d", + fake.discoverHierarchyCalls[0].GetPageSize(), discoverHierarchyPageSize) + } + if fake.discoverHierarchyCalls[1].GetPageToken() != "page-2" { + t.Fatalf("second call page token = %q, want %q", + fake.discoverHierarchyCalls[1].GetPageToken(), "page-2") + } +} + func TestGalaxyDialReturnsGatewayErrorOnRpcFailure(t *testing.T) { fake := &fakeGalaxyServer{failTest: true} client, cleanup := newGalaxyBufconnClient(t, fake) @@ -372,18 +413,20 @@ func newGalaxyBufconnClient(t *testing.T, fake *fakeGalaxyServer) (*GalaxyClient type fakeGalaxyServer struct { pb.UnimplementedGalaxyRepositoryServer - testReply *pb.TestConnectionReply - testAuth string - failTest bool - deployReply *pb.GetLastDeployTimeReply - discoverReply *pb.DiscoverHierarchyReply - watchEvents []*pb.DeployEvent - watchRequest *pb.WatchDeployEventsRequest - watchSendInterval time.Duration - watchHoldOpen bool - browseChildrenCalls []*pb.BrowseChildrenRequest - browseChildrenReplies []*pb.BrowseChildrenReply - browseChildrenError error + testReply *pb.TestConnectionReply + testAuth string + failTest bool + deployReply *pb.GetLastDeployTimeReply + discoverReply *pb.DiscoverHierarchyReply + discoverHierarchyCalls []*pb.DiscoverHierarchyRequest + discoverHierarchyReplies []*pb.DiscoverHierarchyReply + watchEvents []*pb.DeployEvent + watchRequest *pb.WatchDeployEventsRequest + watchSendInterval time.Duration + watchHoldOpen bool + browseChildrenCalls []*pb.BrowseChildrenRequest + browseChildrenReplies []*pb.BrowseChildrenReply + browseChildrenError error } func (s *fakeGalaxyServer) TestConnection(ctx context.Context, req *pb.TestConnectionRequest) (*pb.TestConnectionReply, error) { @@ -405,6 +448,12 @@ func (s *fakeGalaxyServer) GetLastDeployTime(ctx context.Context, req *pb.GetLas } func (s *fakeGalaxyServer) DiscoverHierarchy(ctx context.Context, req *pb.DiscoverHierarchyRequest) (*pb.DiscoverHierarchyReply, error) { + s.discoverHierarchyCalls = append(s.discoverHierarchyCalls, req) + if len(s.discoverHierarchyReplies) > 0 { + reply := s.discoverHierarchyReplies[0] + s.discoverHierarchyReplies = s.discoverHierarchyReplies[1:] + return reply, nil + } if s.discoverReply != nil { return s.discoverReply, nil }