From 1bcc20ba4901af4ee286867973238b3d34fda689 Mon Sep 17 00:00:00 2001 From: Julian LaNeve Date: Wed, 21 Jan 2026 07:02:21 -0800 Subject: [PATCH] fix: allow org switch by ID when org not in paginated list When a user has access to more than 100 organizations, the target org may not appear in the paginated list returned by ListOrganizations. This change adds a fallback that attempts to fetch the organization directly by ID if it's not found in the list. - Add explicit limit of 100 to ListOrganizations - Add new GetOrganization function to fetch org by ID - Add fallback in Switch to try GetOrganization when org not in list Co-Authored-By: Claude Opus 4.5 --- cloud/organization/organization.go | 25 ++++++++- cloud/organization/organization_test.go | 68 ++++++++++++++++++------- 2 files changed, 75 insertions(+), 18 deletions(-) diff --git a/cloud/organization/organization.go b/cloud/organization/organization.go index cef5f37ad..a182a7f54 100644 --- a/cloud/organization/organization.go +++ b/cloud/organization/organization.go @@ -42,7 +42,10 @@ func newTableOut() *printutil.Table { } func ListOrganizations(platformCoreClient astroplatformcore.CoreClient) ([]astroplatformcore.Organization, error) { - organizationListParams := &astroplatformcore.ListOrganizationsParams{} + limit := 100 + organizationListParams := &astroplatformcore.ListOrganizationsParams{ + Limit: &limit, + } resp, err := platformCoreClient.ListOrganizationsWithResponse(http_context.Background(), organizationListParams) if err != nil { return nil, err @@ -56,6 +59,18 @@ func ListOrganizations(platformCoreClient astroplatformcore.CoreClient) ([]astro return orgs, nil } +func GetOrganization(orgID string, platformCoreClient astroplatformcore.CoreClient) (*astroplatformcore.Organization, error) { + resp, err := platformCoreClient.GetOrganizationWithResponse(http_context.Background(), orgID, &astroplatformcore.GetOrganizationParams{}) + if err != nil { + return nil, err + } + err = astroplatformcore.NormalizeAPIError(resp.HTTPResponse, resp.Body) + if err != nil { + return nil, err + } + return resp.JSON200, nil +} + // List all Organizations func List(out io.Writer, platformCoreClient astroplatformcore.CoreClient) error { c, err := config.GetCurrentContext() @@ -173,6 +188,14 @@ func Switch(orgNameOrID string, coreClient astrocore.CoreClient, platformCoreCli targetOrg = &or[i] } } + // If not found in list, try to get org by ID directly + // This handles cases where user has access to more orgs than the list limit + if targetOrg == nil { + org, err := GetOrganization(orgNameOrID, platformCoreClient) + if err == nil && org != nil { + targetOrg = org + } + } } if targetOrg == nil { return errInvalidOrganizationName diff --git a/cloud/organization/organization_test.go b/cloud/organization/organization_test.go index 9eac76c72..a812f2fc8 100644 --- a/cloud/organization/organization_test.go +++ b/cloud/organization/organization_test.go @@ -88,7 +88,7 @@ func (s *Suite) TestList() { s.Run("organization list success", func() { mockClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() buf := new(bytes.Buffer) err := List(buf, mockClient) @@ -98,7 +98,7 @@ func (s *Suite) TestList() { s.Run("organization network error", func() { mockClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(nil, errNetwork).Once() + mockClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(nil, errNetwork).Once() buf := new(bytes.Buffer) err := List(buf, mockClient) s.Contains(err.Error(), "network error") @@ -107,7 +107,7 @@ func (s *Suite) TestList() { s.Run("organization list error", func() { mockClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockErrorResponse, nil).Once() + mockClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockErrorResponse, nil).Once() buf := new(bytes.Buffer) err := List(buf, mockClient) s.Contains(err.Error(), "failed to fetch organizations") @@ -121,7 +121,7 @@ func (s *Suite) TestGetOrganizationSelection() { s.Run("get organiation selection success", func() { mockClient := new(astrocore_mocks.ClientWithResponsesInterface) mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() // mock os.Stdin input := []byte("1") @@ -144,7 +144,7 @@ func (s *Suite) TestGetOrganizationSelection() { s.Run("get organization selection list error", func() { mockClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockErrorResponse, nil).Once() + mockClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockErrorResponse, nil).Once() buf := new(bytes.Buffer) _, err := getOrganizationSelection(buf, mockClient) @@ -154,7 +154,7 @@ func (s *Suite) TestGetOrganizationSelection() { s.Run("get organization selection select error", func() { mockClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() // mock os.Stdin input := []byte("3") @@ -181,7 +181,7 @@ func (s *Suite) TestSwitch() { s.Run("successful switch with name", func() { mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() CheckUserSession = func(c *config.Context, coreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient, out io.Writer) error { return nil } @@ -196,7 +196,7 @@ func (s *Suite) TestSwitch() { s.Run("switching to a current organization", func() { mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() buf := new(bytes.Buffer) err := Switch("org1", mockCoreClient, mockPlatformCoreClient, buf, false) @@ -209,7 +209,7 @@ func (s *Suite) TestSwitch() { s.Run("successful switch without name", func() { mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() CheckUserSession = func(c *config.Context, coreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient, out io.Writer) error { return nil } @@ -234,7 +234,12 @@ func (s *Suite) TestSwitch() { s.Run("failed switch wrong name", func() { mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() + // Mock GetOrganization to return error (org not found by ID either) + mockPlatformCoreClient.On("GetOrganizationWithResponse", mock.Anything, "name-wrong", mock.Anything).Return(&astroplatformcore.GetOrganizationResponse{ + HTTPResponse: &http.Response{StatusCode: 404}, + JSON404: &astroplatformcore.Error{Message: "not found"}, + }, nil).Once() CheckUserSession = func(c *config.Context, coreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient, out io.Writer) error { return nil } @@ -248,7 +253,7 @@ func (s *Suite) TestSwitch() { s.Run("failed switch bad selection", func() { mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() CheckUserSession = func(c *config.Context, coreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient, out io.Writer) error { return nil } @@ -283,7 +288,7 @@ func (s *Suite) TestSwitch() { } mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) - mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() CheckUserSession = func(c *config.Context, coreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient, out io.Writer) error { return nil } @@ -294,6 +299,35 @@ func (s *Suite) TestSwitch() { mockCoreClient.AssertExpectations(s.T()) mockPlatformCoreClient.AssertExpectations(s.T()) }) + + s.Run("successful switch with ID not in list but found via GetOrganization", func() { + // Simulate org not being in the paginated list (e.g., user has >100 orgs) + emptyListResponse := astroplatformcore.ListOrganizationsResponse{ + HTTPResponse: &http.Response{ + StatusCode: 200, + }, + JSON200: &astroplatformcore.OrganizationsPaginated{ + Organizations: []astroplatformcore.Organization{}, + }, + } + mockCoreClient := new(astrocore_mocks.ClientWithResponsesInterface) + mockPlatformCoreClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) + mockPlatformCoreClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&emptyListResponse, nil).Once() + // Mock GetOrganization to return the org by ID + mockPlatformCoreClient.On("GetOrganizationWithResponse", mock.Anything, "org3-id", mock.Anything).Return(&astroplatformcore.GetOrganizationResponse{ + HTTPResponse: &http.Response{StatusCode: 200}, + JSON200: &astroplatformcore.Organization{Id: "org3-id", Name: "org3", Product: &mockOrganizationProduct}, + }, nil).Once() + CheckUserSession = func(c *config.Context, coreClient astrocore.CoreClient, platformCoreClient astroplatformcore.CoreClient, out io.Writer) error { + return nil + } + buf := new(bytes.Buffer) + err := Switch("org3-id", mockCoreClient, mockPlatformCoreClient, buf, false) + s.NoError(err) + s.Equal("\nSuccessfully switched organization\n", buf.String()) + mockCoreClient.AssertExpectations(s.T()) + mockPlatformCoreClient.AssertExpectations(s.T()) + }) } func (s *Suite) TestIsOrgHosted() { @@ -359,7 +393,7 @@ func (s *Suite) TestExportAuditLogs() { s.Run("export audit logs success", func() { mockPlatformClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() mockClient.On("GetOrganizationAuditLogsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockOKAuditLogResponse, nil).Once() err := ExportAuditLogs(mockClient, mockPlatformClient, "", "", 1) s.NoError(err) @@ -369,7 +403,7 @@ func (s *Suite) TestExportAuditLogs() { s.Run("export audit logs and select org success", func() { mockPlatformClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() mockClient.On("GetOrganizationAuditLogsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockOKAuditLogResponse, nil).Once() err := ExportAuditLogs(mockClient, mockPlatformClient, "org1", "", 1) s.NoError(err) @@ -379,7 +413,7 @@ func (s *Suite) TestExportAuditLogs() { s.Run("export failure", func() { mockPlatformClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() mockClient.On("GetOrganizationAuditLogsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(nil, errNetwork).Once() err := ExportAuditLogs(mockClient, mockPlatformClient, "", "", 1) s.Contains(err.Error(), "network error") @@ -389,7 +423,7 @@ func (s *Suite) TestExportAuditLogs() { s.Run("list failure", func() { mockPlatformClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(nil, errNetwork).Once() + mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(nil, errNetwork).Once() err := ExportAuditLogs(mockClient, mockPlatformClient, "org1", "", 1) s.Contains(err.Error(), "network error") mockPlatformClient.AssertExpectations(s.T()) @@ -398,7 +432,7 @@ func (s *Suite) TestExportAuditLogs() { s.Run("organization list error", func() { mockPlatformClient := new(astroplatformcore_mocks.ClientWithResponsesInterface) mockClient := new(astrocore_mocks.ClientWithResponsesInterface) - mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, &astroplatformcore.ListOrganizationsParams{}).Return(&mockOKResponse, nil).Once() + mockPlatformClient.On("ListOrganizationsWithResponse", mock.Anything, mock.Anything).Return(&mockOKResponse, nil).Once() mockClient.On("GetOrganizationAuditLogsWithResponse", mock.Anything, mock.Anything, mock.Anything).Return(&mockOKAuditLogResponseError, nil).Once() err := ExportAuditLogs(mockClient, mockPlatformClient, "", "", 1) s.Contains(err.Error(), "failed to fetch organizations audit logs")