Add convert method for ListUsersByMetadata request

This commit is contained in:
Marco Ardizzone
2025-08-01 12:41:45 +02:00
parent 1182cdcdd9
commit e633ef87b3
5 changed files with 579 additions and 0 deletions

View File

@@ -0,0 +1,115 @@
package convert
import (
"github.com/zitadel/zitadel/internal/api/grpc/filter/v2"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/zerrors"
metadata "github.com/zitadel/zitadel/pkg/grpc/metadata/v2"
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
)
func ListUsersByMetadataRequestToModel(req *user.ListUsersByMetadataRequest, sysDefaults systemdefaults.SystemDefaults) (*query.UserSearchQueries, error) {
offset, limit, asc, err := filter.PaginationPbToQuery(sysDefaults, req.GetPagination())
if err != nil {
return nil, err
}
queries, err := usersByMetadataQueries(req.GetFilters(), 0)
if err != nil {
return nil, err
}
return &query.UserSearchQueries{
SearchRequest: query.SearchRequest{
Offset: offset,
Limit: limit,
Asc: asc,
SortingColumn: usersByMetadataSorting(req.GetSortingColumn()),
},
Queries: queries,
}, nil
}
func usersByMetadataSorting(sortingColumn user.UsersByMetadataSorting) query.Column {
switch sortingColumn {
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_DISPLAY_NAME:
return query.HumanDisplayNameCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_EMAIL:
return query.HumanEmailCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_FIRST_NAME:
return query.HumanFirstNameCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_LAST_NAME:
return query.HumanLastNameCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_METADATA_KEY:
return query.UserMetadataKeyCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_METADATA_VALUE:
return query.UserMetadataValueCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_NICK_NAME:
return query.HumanNickNameCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_STATE:
return query.UserStateCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_TYPE:
return query.UserTypeCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_USER_NAME:
return query.UserUsernameCol
case user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_USER_ID, user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_UNSPECIFIED:
fallthrough
default:
return query.UserIDCol
}
}
func usersByMetadataQueries(queries []*metadata.UserByMetadataSearchFilter, nesting uint) ([]query.SearchQuery, error) {
toReturn := make([]query.SearchQuery, len(queries))
for i, query := range queries {
res, err := userByMetadataQuery(query, nesting)
if err != nil {
return nil, err
}
toReturn[i] = res
}
return toReturn, nil
}
func userByMetadataQuery(q *metadata.UserByMetadataSearchFilter, nesting uint) (query.SearchQuery, error) {
if nesting > 20 {
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-Jhaltm", "Errors.Query.TooManyNestingLevels")
}
switch t := q.GetFilter().(type) {
case *metadata.UserByMetadataSearchFilter_KeyFilter:
return query.NewUserMetadataKeySearchQuery(t.KeyFilter.GetKey(), filter.TextMethodPbToQuery(t.KeyFilter.GetMethod()))
case *metadata.UserByMetadataSearchFilter_ValueFilter:
return query.NewUserMetadataValueSearchQuery(t.ValueFilter.GetValue(), filter.ByteMethodPbToQuery(t.ValueFilter.GetMethod()))
case *metadata.UserByMetadataSearchFilter_AndFilter:
mappedQueries, err := usersByMetadataQueries(t.AndFilter.GetQueries(), nesting+1)
if err != nil {
return nil, err
}
return query.NewUserMetadataAndSearchQuery(mappedQueries)
case *metadata.UserByMetadataSearchFilter_OrFilter:
mappedQueries, err := usersByMetadataQueries(t.OrFilter.GetQueries(), nesting+1)
if err != nil {
return nil, err
}
return query.NewUserMetadataOrSearchQuery(mappedQueries)
case *metadata.UserByMetadataSearchFilter_NotFilter:
mappedQuery, err := userByMetadataQuery(t.NotFilter.GetQuery(), nesting+1)
if err != nil {
return nil, err
}
return query.NewUserMetadataNotSearchQuery(mappedQuery)
default:
return nil, zerrors.ThrowInvalidArgument(nil, "CONV-GG1Jnh", "List.Query.Invalid")
}
}

View File

@@ -0,0 +1,273 @@
package convert
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/zitadel/internal/config/systemdefaults"
"github.com/zitadel/zitadel/internal/query"
filter "github.com/zitadel/zitadel/pkg/grpc/filter/v2"
metadata "github.com/zitadel/zitadel/pkg/grpc/metadata/v2"
user "github.com/zitadel/zitadel/pkg/grpc/user/v2beta"
)
func Test_usersByMetadataSorting(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input user.UsersByMetadataSorting
expected query.Column
}{
{"DisplayName", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_DISPLAY_NAME, query.HumanDisplayNameCol},
{"Email", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_EMAIL, query.HumanEmailCol},
{"FirstName", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_FIRST_NAME, query.HumanFirstNameCol},
{"LastName", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_LAST_NAME, query.HumanLastNameCol},
{"MetadataKey", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_METADATA_KEY, query.UserMetadataKeyCol},
{"MetadataValue", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_METADATA_VALUE, query.UserMetadataValueCol},
{"NickName", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_NICK_NAME, query.HumanNickNameCol},
{"State", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_STATE, query.UserStateCol},
{"Type", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_TYPE, query.UserTypeCol},
{"UserName", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_USER_NAME, query.UserUsernameCol},
{"UserID", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_USER_ID, query.UserIDCol},
{"Unspecified", user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_UNSPECIFIED, query.UserIDCol},
{"Default", user.UsersByMetadataSorting(999), query.UserIDCol},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got := usersByMetadataSorting(tc.input)
assert.Equal(t, tc.expected, got)
})
}
}
func Test_userByMetadataQuery(t *testing.T) {
t.Parallel()
tt := []struct {
name string
input *metadata.UserByMetadataSearchFilter
wantErr bool
}{
{
name: "KeyFilter",
input: &metadata.UserByMetadataSearchFilter{
Filter: &metadata.UserByMetadataSearchFilter_KeyFilter{
KeyFilter: &metadata.MetadataKeyFilter{
Key: "foo",
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
},
},
},
wantErr: false,
},
{
name: "ValueFilter",
input: &metadata.UserByMetadataSearchFilter{
Filter: &metadata.UserByMetadataSearchFilter_ValueFilter{
ValueFilter: &metadata.MetadataValueFilter{
Value: []byte("bar"),
Method: filter.ByteFilterMethod_BYTE_FILTER_METHOD_EQUALS,
},
},
},
wantErr: false,
},
{
name: "AndFilter",
input: &metadata.UserByMetadataSearchFilter{
Filter: &metadata.UserByMetadataSearchFilter_AndFilter{
AndFilter: &metadata.MetadataAndFilter{
Queries: []*metadata.UserByMetadataSearchFilter{
{
Filter: &metadata.UserByMetadataSearchFilter_KeyFilter{
KeyFilter: &metadata.MetadataKeyFilter{
Key: "foo",
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
},
},
},
},
},
},
},
wantErr: false,
},
{
name: "OrFilter",
input: &metadata.UserByMetadataSearchFilter{
Filter: &metadata.UserByMetadataSearchFilter_OrFilter{
OrFilter: &metadata.MetadataOrFilter{
Queries: []*metadata.UserByMetadataSearchFilter{
{
Filter: &metadata.UserByMetadataSearchFilter_ValueFilter{
ValueFilter: &metadata.MetadataValueFilter{
Value: []byte("baz"),
Method: filter.ByteFilterMethod_BYTE_FILTER_METHOD_EQUALS,
},
},
},
},
},
},
},
wantErr: false,
},
{
name: "NotFilter",
input: &metadata.UserByMetadataSearchFilter{
Filter: &metadata.UserByMetadataSearchFilter_NotFilter{
NotFilter: &metadata.MetadataNotFilter{
Query: &metadata.UserByMetadataSearchFilter{
Filter: &metadata.UserByMetadataSearchFilter_KeyFilter{
KeyFilter: &metadata.MetadataKeyFilter{
Key: "not",
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
},
},
},
},
},
},
wantErr: false,
},
{
name: "TooManyNestingLevels",
input: &metadata.UserByMetadataSearchFilter{
Filter: &metadata.UserByMetadataSearchFilter_AndFilter{
AndFilter: &metadata.MetadataAndFilter{
Queries: []*metadata.UserByMetadataSearchFilter{},
},
},
},
wantErr: true,
},
{
name: "InvalidFilter",
input: &metadata.UserByMetadataSearchFilter{},
wantErr: true,
},
}
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
nesting := uint(0)
if tc.name == "TooManyNestingLevels" {
nesting = 21
}
got, err := userByMetadataQuery(tc.input, nesting)
if tc.wantErr {
require.Error(t, err)
assert.Nil(t, got)
} else {
require.NoError(t, err)
assert.NotNil(t, got)
}
})
}
}
func Test_usersByMetadataQueries(t *testing.T) {
t.Parallel()
t.Run("empty", func(t *testing.T) {
t.Parallel()
queries, err := usersByMetadataQueries([]*metadata.UserByMetadataSearchFilter{}, 0)
require.NoError(t, err)
assert.Len(t, queries, 0)
})
t.Run("single valid", func(t *testing.T) {
t.Parallel()
queries, err := usersByMetadataQueries([]*metadata.UserByMetadataSearchFilter{
{
Filter: &metadata.UserByMetadataSearchFilter_KeyFilter{
KeyFilter: &metadata.MetadataKeyFilter{
Key: "foo",
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
},
},
},
}, 0)
require.NoError(t, err)
assert.Len(t, queries, 1)
})
t.Run("invalid filter", func(t *testing.T) {
t.Parallel()
queries, err := usersByMetadataQueries([]*metadata.UserByMetadataSearchFilter{
{},
}, 0)
require.Error(t, err)
assert.Nil(t, queries)
})
}
func Test_ListUsersByMetadataRequestToModel(t *testing.T) {
t.Parallel()
sysDefaults := systemdefaults.SystemDefaults{
MaxQueryLimit: 100,
}
t.Run("valid", func(t *testing.T) {
t.Parallel()
req := &user.ListUsersByMetadataRequest{
Pagination: &filter.PaginationRequest{
Limit: 10,
Offset: 5,
Asc: true,
},
SortingColumn: user.UsersByMetadataSorting_USERS_BY_METADATA_SORT_BY_EMAIL,
Filters: []*metadata.UserByMetadataSearchFilter{
{
Filter: &metadata.UserByMetadataSearchFilter_KeyFilter{
KeyFilter: &metadata.MetadataKeyFilter{
Key: "foo",
Method: filter.TextFilterMethod_TEXT_FILTER_METHOD_EQUALS,
},
},
},
},
}
model, err := ListUsersByMetadataRequestToModel(req, sysDefaults)
require.NoError(t, err)
assert.NotNil(t, model)
assert.EqualValues(t, 5, model.Offset)
assert.EqualValues(t, 10, model.Limit)
assert.True(t, model.Asc)
assert.Equal(t, query.HumanEmailCol, model.SortingColumn)
assert.Len(t, model.Queries, 1)
})
t.Run("invalid pagination", func(t *testing.T) {
t.Parallel()
req := &user.ListUsersByMetadataRequest{
Pagination: &filter.PaginationRequest{
Limit: 200,
},
Filters: []*metadata.UserByMetadataSearchFilter{},
}
model, err := ListUsersByMetadataRequestToModel(req, sysDefaults)
require.Error(t, err)
assert.Nil(t, model)
})
t.Run("invalid filter", func(t *testing.T) {
t.Parallel()
req := &user.ListUsersByMetadataRequest{
Pagination: &filter.PaginationRequest{
Limit: 1,
},
Filters: []*metadata.UserByMetadataSearchFilter{
{},
},
}
model, err := ListUsersByMetadataRequestToModel(req, sysDefaults)
require.Error(t, err)
assert.Nil(t, model)
})
}