diff --git a/README.md b/README.md index b1569d7f..a65fa9ec 100644 --- a/README.md +++ b/README.md @@ -807,7 +807,7 @@ userRes, err := descopeClient.Management.User().LoadByUserID(context.Background( // Search all users, optionally according to tenant and/or role filter // Results can be paginated using the limit and page parameters -usersResp, err := descopeClient.Management.User().SearchAll(context.Background(), &descope.UserSearchOptions{TenantIDs: []string{"my-tenant-id"}}) +usersResp, total, err := descopeClient.Management.User().SearchAll(context.Background(), &descope.UserSearchOptions{TenantIDs: []string{"my-tenant-id"}}) if err == nil { for _, user := range usersResp { // Do something diff --git a/descope/internal/mgmt/user.go b/descope/internal/mgmt/user.go index d2aa2f96..1816bfe6 100644 --- a/descope/internal/mgmt/user.go +++ b/descope/internal/mgmt/user.go @@ -214,7 +214,7 @@ func (u *user) load(ctx context.Context, loginID, userID string) (*descope.UserR return unmarshalUserResponse(res) } -func (u *user) SearchAll(ctx context.Context, options *descope.UserSearchOptions) ([]*descope.UserResponse, error) { +func (u *user) SearchAll(ctx context.Context, options *descope.UserSearchOptions) ([]*descope.UserResponse, int, error) { // Init empty options if non given if options == nil { options = &descope.UserSearchOptions{} @@ -222,18 +222,18 @@ func (u *user) SearchAll(ctx context.Context, options *descope.UserSearchOptions // Make sure limit is non-negative if options.Limit < 0 { - return nil, utils.NewInvalidArgumentError("limit") + return nil, 0, utils.NewInvalidArgumentError("limit") } // Make sure page is non-negative if options.Page < 0 { - return nil, utils.NewInvalidArgumentError("page") + return nil, 0, utils.NewInvalidArgumentError("page") } req := makeSearchAllRequest(options) res, err := u.client.DoPostRequest(ctx, api.Routes.ManagementUserSearchAll(), req, nil, u.conf.ManagementKey) if err != nil { - return nil, err + return nil, 0, err } return unmarshalUserSearchAllResponse(res) } @@ -833,15 +833,16 @@ func unmarshalUserBatchResponse(res *api.HTTPResponse) (*descope.UsersBatchRespo return ures, err } -func unmarshalUserSearchAllResponse(res *api.HTTPResponse) ([]*descope.UserResponse, error) { +func unmarshalUserSearchAllResponse(res *api.HTTPResponse) ([]*descope.UserResponse, int, error) { ures := struct { Users []*descope.UserResponse + Total int }{} err := utils.Unmarshal([]byte(res.BodyStr), &ures) if err != nil { - return nil, err + return nil, 0, err } - return ures.Users, nil + return ures.Users, ures.Total, nil } func unmarshalProviderTokenResponse(res *api.HTTPResponse) (*descope.ProviderTokenResponse, error) { diff --git a/descope/internal/mgmt/user_test.go b/descope/internal/mgmt/user_test.go index 825e05be..ae2c4220 100644 --- a/descope/internal/mgmt/user_test.go +++ b/descope/internal/mgmt/user_test.go @@ -532,6 +532,7 @@ func TestSearchAllUsersSuccess(t *testing.T) { "users": []map[string]any{{ "email": "a@b.c", }}, + "total": 85, } tenantIDs := []string{"t1"} roleNames := []string{"role1"} @@ -550,7 +551,7 @@ func TestSearchAllUsersSuccess(t *testing.T) { require.EqualValues(t, "blue", req["text"]) require.EqualValues(t, []interface{}([]interface{}{map[string]interface{}{"Desc": true, "Field": "nono"}, map[string]interface{}{"Desc": false, "Field": "lolo"}}), req["sort"]) }, response)) - res, err := m.User().SearchAll(context.Background(), &descope.UserSearchOptions{ + res, total, err := m.User().SearchAll(context.Background(), &descope.UserSearchOptions{ Statuses: []descope.UserStatus{descope.UserStatusDisabled}, TenantIDs: tenantIDs, Roles: roleNames, @@ -568,26 +569,30 @@ func TestSearchAllUsersSuccess(t *testing.T) { require.NotNil(t, res) require.Len(t, res, 1) require.Equal(t, "a@b.c", res[0].Email) + require.Equal(t, 85, total) } func TestSearchAllUsersError(t *testing.T) { m := newTestMgmt(nil, helpers.DoBadRequest(nil)) - res, err := m.User().SearchAll(context.Background(), nil) + res, total, err := m.User().SearchAll(context.Background(), nil) require.Error(t, err) require.Nil(t, res) + require.Zero(t, total) } func TestSearchAllUsersBadRequest(t *testing.T) { m := newTestMgmt(nil, helpers.DoBadRequest(nil)) - res, err := m.User().SearchAll(context.Background(), &descope.UserSearchOptions{Limit: -1}) + res, total, err := m.User().SearchAll(context.Background(), &descope.UserSearchOptions{Limit: -1}) require.ErrorIs(t, err, descope.ErrInvalidArguments) require.Contains(t, err.Error(), "limit") require.Nil(t, res) + require.Zero(t, total) - res, err = m.User().SearchAll(context.Background(), &descope.UserSearchOptions{Page: -1}) + res, total, err = m.User().SearchAll(context.Background(), &descope.UserSearchOptions{Page: -1}) require.ErrorIs(t, err, descope.ErrInvalidArguments) require.Contains(t, err.Error(), "page") require.Nil(t, res) + require.Zero(t, total) } func TestUserActivateSuccess(t *testing.T) { diff --git a/descope/sdk/mgmt.go b/descope/sdk/mgmt.go index 9939d786..956aa851 100644 --- a/descope/sdk/mgmt.go +++ b/descope/sdk/mgmt.go @@ -223,7 +223,8 @@ type User interface { // The options optional parameter allows to fine-tune the search filters // and results. Using nil will result in a filter-less query with a set amount of // results. - SearchAll(ctx context.Context, options *descope.UserSearchOptions) ([]*descope.UserResponse, error) + // Returns slice of users and total number of users for the query + SearchAll(ctx context.Context, options *descope.UserSearchOptions) ([]*descope.UserResponse, int, error) // Activate an existing user. Activate(ctx context.Context, loginID string) (*descope.UserResponse, error) diff --git a/descope/tests/mocks/mgmt/managementmock.go b/descope/tests/mocks/mgmt/managementmock.go index 259d11c5..60d87853 100644 --- a/descope/tests/mocks/mgmt/managementmock.go +++ b/descope/tests/mocks/mgmt/managementmock.go @@ -269,9 +269,10 @@ type MockUser struct { LoadResponse *descope.UserResponse LoadError error - SearchAllAssert func(options *descope.UserSearchOptions) - SearchAllResponse []*descope.UserResponse - SearchAllError error + SearchAllAssert func(options *descope.UserSearchOptions) + SearchAllResponse []*descope.UserResponse + SearchAllTotalResponse int + SearchAllError error ActivateAssert func(loginID string) ActivateResponse *descope.UserResponse @@ -489,11 +490,11 @@ func (m *MockUser) LogoutUserByUserID(_ context.Context, userID string) error { return m.LogoutError } -func (m *MockUser) SearchAll(_ context.Context, options *descope.UserSearchOptions) ([]*descope.UserResponse, error) { +func (m *MockUser) SearchAll(_ context.Context, options *descope.UserSearchOptions) ([]*descope.UserResponse, int, error) { if m.SearchAllAssert != nil { m.SearchAllAssert(options) } - return m.SearchAllResponse, m.SearchAllError + return m.SearchAllResponse, m.SearchAllTotalResponse, m.SearchAllError } func (m *MockUser) Activate(_ context.Context, loginID string) (*descope.UserResponse, error) {